Color - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-color?language=swift&platform=ios#setup # Color In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/creative-sdk)'s CreativeEngine to modify a fill block's solid color through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ``` public func setColor(_ id: DesignBlockID, property: String, color: Color) throws ``` Set a color property of the given design block to the given value. * `id`: The block whose property should be set. * `property`: The name of the property to set. * `color`: The value to set. ``` try engine.block.setColor(solidColor, property: "fill/color/value", color: rgbaBlack) ``` ``` public func getColor(_ id: DesignBlockID, property: String) throws -> Color ``` Get the value of a color property of the given design block. * `id`: The block whose property should be queried. * `property`: The name of the property to query. * Returns: The color value of the property. ``` let currentColor: Color = try engine.block.getColor(block, property: "fill/color/value") ``` Basic Configuration Settings - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/mobile-editor/configuration/basics/?platform=ios&language=swift # Basic Configuration Settings In this example, we will show you how to make basic configurations for the mobile editor. The example is based on the `Design Editor`, however, it is exactly the same for all the other [solutions](/docs/cesdk/mobile-editor/solutions/). Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-configuration-basics/BasicEditorSolution.swift). * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-configuration-basics/BasicEditorSolution.swift) ## Configuration All the basic configuration settings are part of the `EngineSettings` which are required to initialize the editor. ``` DesignEditor(settings) ``` * `license` - the license to activate the [Engine](/docs/cesdk/engine/quickstart/) with. ``` license: secrets.licenseKey, ``` * `userID` - an optional unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU). Especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. Providing this aids in better data accuracy. The default value is `nil`. ``` userID: "", ``` * `baseURL` - is used to initialize the engine's [`basePath` setting](/docs/cesdk/engine/api/editor-settings/#settings) before the editor's [`onCreate` callback](/docs/cesdk/mobile-editor/configuration/callbacks/) is run. It is the foundational URL for constructing absolute paths from relative ones. This URL enables the loading of specific scenes or assets using their relative paths. The default value is pointing at the versioned IMG.LY CDN `https://cdn.img.ly/packages/imgly/cesdk-engine/1.43.0/assets` but it should be changed in production environments. ``` baseURL: URL(string: "https://cdn.img.ly/packages/imgly/cesdk-engine/1.43.0/assets")! ``` Fills - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-fills?language=swift&platform=ios#setup # Fills In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a block's fill through the `block` API. The fill defines the visual contents within a block's shape. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Creating a Fill To create a fill simply use `createFill`. ``` let solidColor = try engine.block.createFill(.color) ``` ``` public func createFill(_ type: FillType) throws -> DesignBlockID ``` Create a new fill. * `type:`: The type of the fill object that shall be created. * Returns: The created fill's handle. ``` let solidColor = try engine.block.createFill(.color) ``` We currently support the following fill types: * `FillType.color` * `FillType.linearGradient` * `FillType.radialGradient` * `FillType.conicalGradient` * `FillType.image` * `FillType.video` * `FillType.pixelStream` ``` let solidColor = try engine.block.createFill(.color) ``` ## Functions You can configure fills just like you configure design blocks. See [Modify Properties](/docs/cesdk/engine/api/block-properties/) for more detail. ``` try engine.block.setColor(solidColor, property: "fill/color/value", color: .rgba(r: 0.44, g: 0.76, b: 0.76, a: 1.0)) ``` ``` public func getFill(_ id: DesignBlockID) throws -> DesignBlockID ``` Returns the block containing the fill properties of the given block. * `id:`: The block whose fill block should be returned. * Returns: The block that currently defines the given block's fill. ``` let previousFill = try engine.block.getFill(block) ``` Remember to first destroy the previous fill if you don't need it any more. A single fill can also be connected to multiple design blocks. This way, modifying the properties of the fill will apply the changes to all connected design blocks at once. ``` public func setFill(_ id: DesignBlockID, fill: DesignBlockID) throws ``` Sets the block containing the fill properties of the given block. * Note: The previous fill block is not destroyed automatically. Required scope: "fill/change", "fill/changeType" * `id`: The block whose fill should be changed. * `fill`: The new fill. ``` try engine.block.setFill(block, fill: solidColor) ``` ``` public func supportsFill(_ id: DesignBlockID) throws -> Bool ``` Query if the given block has fill color properties. * `id:`: The block to query. * Returns: `true`, if the block has fill color properties, an error otherwise. ``` try engine.block.supportsFill(block) ``` ``` public func setFillEnabled(_ id: DesignBlockID, enabled: Bool) throws ``` Enable or disable the fill of the given design block. Required scope: "fill/change" * `id`: The block whose fill should be enabled or disabled. * `enabled`: If `true`, the fill will be enabled. ``` try engine.block.setFillEnabled(block, enabled: false) ``` ``` public func isFillEnabled(_ id: DesignBlockID) throws -> Bool ``` Query if the fill of the given design block is enabled. * `id:`: The block whose fill state should be queried. * Returns: A result holding the fill state or an error. ``` try engine.block.isFillEnabled(block) ``` Using Cutouts - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/cutouts?language=swift&platform=ios#note # Using Cutouts 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 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. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-cutouts?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Cutouts&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-cutouts). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-cutouts?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Cutouts&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-cutouts) ## Setup the scene We first create a new scene with a new page. ``` 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. ``` 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`. #### 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. ​ ``` 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. ``` engine.editor.setSpotColor(name: "CutContour", r: 0.0, g: 0.0, b: 1.0) ``` Modify Appearance - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-appearance?language=swift&platform=ios#setup # Modify Appearance In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a blocks appearance through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Common Properties Common properties are properties that occur on multiple block types. For instance, fill color properties are available for all the shape blocks and the text block. That's why we built convenient setter and getter functions for these properties. So you don't have to use the generic setters and getters and don't have to provide a specific property path. There are also `has*` functions to query if a block supports a set of common properties. ### Opacity Set the translucency of the entire block. ``` public func supportsOpacity(_ id: DesignBlockID) throws -> Bool ``` Query if the given block has an opacity. * `id:`: The block to query. * Returns: `true`, if the block has an opacity. ``` try engine.block.supportsOpacity(image) ``` ``` public func setOpacity(_ id: DesignBlockID, value: Float) throws ``` Set the opacity of the given design block. Required scope: "layer/opacity" * `id`: The block whose opacity should be set. * `value`: The opacity to be set. The valid range is 0 to 1. ``` try engine.block.setOpacity(image, value: 0.5) ``` ``` public func getOpacity(_ id: DesignBlockID) throws -> Float ``` Get the opacity of the given design block. * `id:`: The block whose opacity should be queried. * Returns: The opacity. ``` try engine.block.getOpacity(image) ``` ### Blend Mode Define the blending behaviour of a block. ``` public func supportsBlendMode(_ id: DesignBlockID) throws -> Bool ``` Query if the given block has a blend mode. * `id:`: The block to query. * Returns: `true`, if the block has a blend mode. ``` try engine.block.supportsBlendMode(image) ``` ``` public func setBlendMode(_ id: DesignBlockID, mode: BlendMode) throws ``` Set the blend mode of the given design block. Required scope: "layer/blendMode" * `id`: The block whose blend mode should be set. * `mode`: The blend mode to be set. ``` try engine.block.setBlendMode(image, mode: .multiply) ``` ``` public func getBlendMode(_ id: DesignBlockID) throws -> BlendMode ``` Get the blend mode of the given design block. * `id:`: The block whose blend mode should be queried. * Returns: The blend mode. ``` try engine.block.getBlendMode(image) ``` ### Background Color Manipulate the background of a block. To understand the difference between fill and background color take the text block. The glyphs of the text itself are colored by the fill color. The rectangular background given by the bounds of the block on which the text is drawn is colored by the background color. ``` public func supportsBackgroundColor(_ id: DesignBlockID) throws -> Bool ``` Query if the given block has background color properties. * `id:`: The block to query. * Returns: `true`, if the block has background color properties. ``` if try engine.block.supportsBackgroundColor(image) { ``` ``` public func setBackgroundColor(_ id: DesignBlockID, r: Float, g: Float, b: Float, a: Float = 1) throws ``` Set the background color of the given design block. Required scope: "fill/change" * `id`: The block whose background color should be set. * `r`: The red color component in the range of 0 to 1. * `g`: The green color component in the range of 0 to 1. * `b`: The blue color component in the range of 0 to 1. * `a`: The alpha color component in the range of 0 to 1. ``` try engine.block.setBackgroundColor(page, r: 1, g: 0, b: 0, a: 1) // Red ``` ``` public func getBackgroundColor(_ id: DesignBlockID) throws -> RGBA ``` Get the background color of the given design block. * `id:`: The block whose background color should be queried. * Returns: The background color. ``` try engine.block.getBackgroundColor(page) ``` ``` public func setBackgroundColorEnabled(_ id: DesignBlockID, enabled: Bool) throws ``` Enable or disable the background of the given design block. Required scope: "fill/change" * `id`: The block whose background should be enabled or disabled. * `enabled`: If `true`, the background will be enabled. ``` try engine.block.setBackgroundColorEnabled(page, enabled: true) ``` ``` public func isBackgroundColorEnabled(_ id: DesignBlockID) throws -> Bool ``` Query if the background of the given design block is enabled. * `id:`: The block whose background state should be queried. * Returns: `true`, if background is enabled. ``` try engine.block.isBackgroundColorEnabled(page) ``` Kind - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-kind?language=swift&platform=ios#setup # Kind In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a and query the kind property of design blocks through the `block` API. The `kind` of a design block is a custom string that can be assigned to a block in order to categorize it and distinguish it from other blocks that have the same type. The user interface can then customize its appearance based on the kind of the selected blocks. It can also be used for automation use cases in order to process blocks in a different way based on their kind. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ``` public func setKind(_ id: DesignBlockID, kind: String) throws ``` Get the kind of the given block, fails if the block is invalid. * `id`: The block to query. * `kind`: The block's kind. ``` try engine.block.setKind(text, kind: "title") ``` ``` public func getKind(_ id: DesignBlockID) throws -> String ``` Get the kind of the given block, fails if the block is invalid. * `id:`: The block to query. * Returns: The block's kind. ``` let kind = try engine.block.getKind(text) ``` ``` public func find(byKind kind: String) throws -> [DesignBlockID] ``` Finds all blocks with the given kind. * `kind:`: The kind to search for. * Returns: A list of block ids. ``` let allTitles = try engine.block.find(byKind: "title") ``` How to Manage Scopes - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/scopes?language=swift&platform=ios#setup # How to Manage Scopes CE.SDK allows you to control which parts of a block can be manipulated. Scopes describe different aspects of a block, e.g. layout or style and can be enabled or disabled for every single block. There's also the option to control a scope globally. When configuring a scope globally you can set an override to always allow or deny a certain type of manipulation for every block. Or you can configure the global scope to defer to the individual block scopes. Initially, the block-level scopes are all disabled while at the global level all scopes are set to `"Allow"`. This overrides the block-level and allows for any kind of manipulation. If you want to implement a limited editing mode in your software you can set the desired scopes on the blocks you want the user to manipulate and then restrict the available actions by globally setting the scopes to `"Defer"`. In the same way you can prevent any manipulation of properties covered by a scope by setting the respective global scope to `"Deny"`. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. ``` let scene = try await engine.scene.create(fromImage: .init(string: "https://img.ly/static/ubq_samples/imgly_logo.jpg")!) let block = try engine.block.find(byType: .graphic).first! ``` ## Available Scopes You can retrieve all available scopes by calling `try engine.editor.findAllScopes()`. ``` let scopes = try engine.editor.findAllScopes() ``` We currently support the following scopes: Scope Explanation Scope `"layer/move"` Explanation Whether the block's position can be changed Scope `"layer/resize"` Explanation Whether the block can be resized Scope `"layer/rotate"` Explanation Whether the block's rotation can be changed Scope `"layer/flip"` Explanation Whether the block can be flipped Scope `"layer/crop"` Explanation Whether the block's content can be cropped Scope `"layer/clipping"` Explanation Whether the block's clipping can be changed Scope `"layer/opacity"` Explanation Whether the block's opacity can be changed Scope `"layer/blendMode"` Explanation Whether the block's blend mode can be changed Scope `"layer/visibility"` Explanation Whether the block's visibility can be changed Scope `"appearance/adjustments"` Explanation Whether the block's adjustments can be changed Scope `"appearance/filter"` Explanation Whether the block's filter can be changed Scope `"appearance/effect"` Explanation Whether the block's effect can be changed Scope `"appearance/blur"` Explanation Whether the block's blur can be changed Scope `"appearance/shadow"` Explanation Whether the block's shadow can be changed Scope `"lifecycle/destroy"` Explanation Whether the block can be deleted Scope `"lifecycle/duplicate"` Explanation Whether the block can be duplicated Scope `"editor/add"` Explanation Whether new blocks can be added Scope `"editor/select"` Explanation Whether a block can be selected or not Scope `"fill/change"` Explanation Whether the block's fill can be changed Scope `"stroke/change"` Explanation Whether the block's stroke can be changed Scope `"shape/change"` Explanation Whether the block's shape can be changed Scope `"text/edit"` Explanation Whether the block's text can be changed Scope `"text/character"` Explanation Whether the block's text properties can be changed ## Managing Scopes First, we globally defer the `"layer/move"` scope to the block-level using `try engine.editor.setGlobalScope(key: "layer/move", value: .defer)`. Since all blocks default to having their scopes set to `false` initially, modifying the layout properties of any block will fail at this point. Value Explanation Value `.allow` Explanation Manipulation of properties covered by the scope is always allowed Value `.deny` Explanation Manipulation of properties covered by the scope is always denied Value `.defer` Explanation Permission is deferred to the scope of the individual blocks ``` /* Let the global scope defer to the block-level. */ try engine.editor.setGlobalScope(key: "layer/move", value: .defer) /* Manipulation of layout properties of any block will fail at this point. */ do { try engine.block.setPositionX(block, value: 100) // Not allowed } catch { print(error.localizedDescription) } ``` We can verify the current state of the global `"layer/move"` scope using `try engine.editor.getGlobalScope(key: "layer/move")`. ``` /* This will return `.defer`. */ try engine.editor.getGlobalScope(key: "layer/move") ``` Now we can allow the `"layer/move"` scope for a single block by setting it to `true` using `func setScopeEnabled(_ id: DesignBlockID, key: String, enabled: Bool) throws`. ``` /* Allow the user to control the layout properties of the image block. */ try engine.block.setScopeEnabled(block, key: "layer/move", enabled: true) /* Manipulation of layout properties of any block is now allowed. */ do { try engine.block.setPositionX(block, value: 100) // Allowed } catch { print(error.localizedDescription) } ``` Again we can verify this change by calling `func isScopeEnabled(_ id: DesignBlockID, key: String) throws -> Bool`. ``` /* Verify that the "layer/move" scope is now enabled for the image block. */ try engine.block.isScopeEnabled(block, key: "layer/move") ``` Finally, `func isAllowedByScope(_ id: DesignBlockID, key: String) throws -> Bool` will allow us to verify a block's final scope state by taking both the global state as well as block-level state into account. ``` /* This will return true as well since the global scope is set to `.defer`. */ try engine.block.isAllowedByScope(block, key: "layer/move") ``` How to Use Animations - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/using-animations/?platform=ios&language=swift Platform Web iOS Catalyst macOS Android Language Swift Platform: iOS Language: Swift [ Previous Using Shapes ](/docs/cesdk/engine/guides/using-shapes/)[ Next Managing Colors ](/docs/cesdk/engine/guides/colors/) Using the Video Editor - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/mobile-editor/solutions/video-editor/?platform=ios&language=swift # Using the Video Editor `Video Editor` is built to support versatile video editing capabilities for a broad range of video applications. In this example, we will show you how to initialize the `Video Editor` solution for the mobile editor on iOS. The mobile editor is implemented entirely with SwiftUI and this example assumes that you also use SwiftUI to integrate it, however, you can check the UIKit implementation sample on the [quickstart](/docs/cesdk/mobile-editor/quickstart?framework=uikit) page. It can be also applied to this example. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-solutions-video-editor/VideoEditorSolution.swift). * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-solutions-video-editor/VideoEditorSolution.swift) ## Import After [adding the IMGLYUI Swift Package](/docs/cesdk/mobile-editor/quickstart/#using-swift-package-manager) to your app. You can get started right away by importing the editor module into your own code. ``` import IMGLYVideoEditor ``` ## Initialization The editor is initialized with `EngineSettings` which are used to initialize the underlying [Engine](/docs/cesdk/engine/quickstart/). The license key that you received from IMG.LY is the only required parameter. Additionally, you should provide an optional unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU) and it is especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. For more details on how to configure the editor, visit the [configuration](/docs/cesdk/mobile-editor/configuration/) page. ``` let settings = EngineSettings(license: secrets.licenseKey, userID: "") var editor: some View { VideoEditor(settings) } ``` ## Presentation In this integration example the editor is presented as a modal view after tapping a button. Check out the [quickstart](/docs/cesdk/mobile-editor/quickstart/#environment) page for details on the expected environment for the editor and the `ModalEditor` helper. ``` @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } ``` Integrate the Mobile Editor - CE.SDK | IMG.LY Docs [ios/swiftui/swift] https://img.ly/docs/cesdk/mobile-editor/quickstart?platform=ios&language=swift&framework=swiftui # Integrate the Mobile Editor In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s mobile editor in your iOS app. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-quickstart/). * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-quickstart/) ### Requirements The mobile editor requires iOS 16 and Swift 5.10 (Xcode 15.4) or later. ### Using Swift Package Manager If you use [Swift Package Manager](https://github.com/apple/swift-package-manager) to build your app and want to integrate the Creative Engine and UI modules using your regular workflows, add the [IMGLYUI Swift Package](https://github.com/imgly/IMGLYUI-swift) as a dependency to your project. ![](/docs/cesdk/3c5227dab88fd8e5a0ca5fb8deeb16e1/spm-ui.png) This package provides multiple library products. Add the default `IMGLYUI` library to your app target to add all available UI modules included in this package to your app. To keep your app size minimal, only add the library product that you need, e.g., only add the `IMGLYDesignEditor` library if you need to `import IMGLYDesignEditor` in your code. On the _General_ page of your app target's Xcode project settings the _Frameworks, Libraries, and Embedded Content_ section lists all used library products. ## Usage In this example, the basic usage of the mobile editor is going to be demonstrated using the [Design Editor](/docs/cesdk/mobile-editor/solutions/design-editor/) solution, however, it is exactly the same for all the other [solutions](/docs/cesdk/mobile-editor/solutions/). ### Import You can get started right away by importing the editor module into your own code. ``` import IMGLYDesignEditor ``` ### Initialization Each editor is initialized with `EngineSettings`. You need to provide the license key that you received from IMG.LY. Optionally, you can provide a unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU) and it is especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. ``` DesignEditor(.init(license: secrets.licenseKey, userID: "")) ``` ### Environment Each editor view needs to be embedded in a navigation environment as it manages the toolbar used by the editor. In this example, the `ModalEditor` helper provides the navigation environment. Alternatively, the editor could also be used as the destination of a `NavigationLink`. ``` ModalEditor { DesignEditor(.init(license: secrets.licenseKey, userID: "")) } ``` The `ModalEditor` helper injects the dismiss button for the editor. If the `ModalEditor` helper is not used and the editor is setup as the destination of a `NavigationLink` the back button is provided by the navigation hierarchy. ``` NavigationView { editor() .onPreferenceChange(BackButtonHiddenKey.self) { newValue in isBackButtonHidden = newValue } .toolbar { ToolbarItem(placement: .navigationBarLeading) { if !isBackButtonHidden { dismissButton } } } } .navigationViewStyle(.stack) ``` In this integration example the editor is presented as a modal view after tapping a button. ``` Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { DesignEditor(.init(license: secrets.licenseKey, userID: "")) } } ``` That is all. Check all the available [solutions](/docs/cesdk/mobile-editor/solutions/) in order to decide which solution fits you best. For more than basic configuration, check out all the available [configurations](/docs/cesdk/mobile-editor/configuration/). Animations - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-animations/?platform=ios&language=swift Platform Web Node.JS iOS Catalyst macOS Android Language Swift Platform: iOS Language: Swift [ Previous Types ](/docs/cesdk/engine/api/block-types/)[ Next Animation Types ](/docs/cesdk/engine/api/block-animation-types/) Strokes - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-strokes?language=swift&platform=ios#setup # Strokes In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify strokes through the `block` API. Strokes can be added to any shape or text and stroke styles are varying from plain solid lines to dashes and gaps of varying lengths and can have different end caps. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Strokes ``` public func supportsStroke(_ id: DesignBlockID) throws -> Bool ``` Query if the given block has a stroke property. * `id:`: The block to query. * Returns: `true` if the block has a stroke property. ``` if try engine.block.supportsStroke(block) { ``` ``` public func setStrokeEnabled(_ id: DesignBlockID, enabled: Bool) throws ``` Enable or disable the stroke of the given design block. Required scope: "stroke/change" * `id`: The block whose stroke should be enabled or disabled. * `enabled`: If `true`, the stroke will be enabled. ``` try engine.block.setStrokeEnabled(block, enabled: true) ``` ``` public func isStrokeEnabled(_ id: DesignBlockID) throws -> Bool ``` Query if the stroke of the given design block is enabled. * `id:`: The block whose stroke state should be queried. * Returns: `true` if the block's stroke is enabled. ``` let strokeIsEnabled = try engine.block.isStrokeEnabled(block) ``` ``` public func setStrokeColor(_ id: DesignBlockID, color: Color) throws ``` Set the stroke color of the given design block. Required scope: "stroke/change" * `id`: The block whose stroke color should be set. * `color`: The color to set. ``` try engine.block.setStrokeColor(block, color: .rgba(r: 1.0, g: 0.75, b: 0.8, a: 1.0)) ``` ``` public func getStrokeColor(_ id: DesignBlockID) throws -> Color ``` Get the stroke color of the given design block. * `id:`: The block whose stroke color should be queried. * Returns: The stroke color. ``` let strokeColor = try engine.block.getStrokeColor(block) ``` ``` public func setStrokeWidth(_ id: DesignBlockID, width: Float) throws ``` Set the stroke width of the given design block. Required scope: "stroke/change" * `id`: The block whose stroke width should be set. * `width`: The stroke width to be set. ``` try engine.block.setStrokeWidth(block, width: 5) ``` ``` public func getStrokeWidth(_ id: DesignBlockID) throws -> Float ``` Get the stroke width of the given design block. * `id:`: The block whose stroke width should be queried. * Returns: The stroke's width. ``` let strokeWidth = try engine.block.getStrokeWidth(block) ``` ``` public func setStrokeStyle(_ id: DesignBlockID, style: StrokeStyle) throws ``` Set the stroke style of the given design block. Required scope: "stroke/change" * `id`: The block whose stroke style should be set. * `style`: The stroke style to be set. ``` try engine.block.setStrokeStyle(block, style: .dashed) ``` ``` public func getStrokeStyle(_ id: DesignBlockID) throws -> StrokeStyle ``` Get the stroke style of the given design block. * `id:`: The block whose stroke style should be queried. * Returns: The stroke's style. ``` let strokeStlye = try engine.block.getStrokeStyle(block) ``` ``` public func setStrokePosition(_ id: DesignBlockID, position: StrokePosition) throws ``` Set the stroke position of the given design block. Required scope: "stroke/change" * `id`: The block whose stroke position should be set. * `position`: The stroke position to be set. ``` try engine.block.setStrokePosition(block, position: .outer) ``` ``` public func getStrokePosition(_ id: DesignBlockID) throws -> StrokePosition ``` Get the stroke position of the given design block. * `id:`: The block whose stroke position should be queried. * Returns: The stroke position. ``` let strokePosition = try engine.block.getStrokePosition(block) ``` ``` public func setStrokeCornerGeometry(_ id: DesignBlockID, cornerGeometry: StrokeCornerGeometry) throws ``` Set the stroke corner geometry of the given design block. Required scope: "stroke/change" * `id`: The block whose stroke join geometry should be set. * `cornerGeometry`: The stroke join geometry to be set. ``` try engine.block.setStrokeCornerGeometry(block, cornerGeometry: .round) ``` ``` public func getStrokeCornerGeometry(_ id: DesignBlockID) throws -> StrokeCornerGeometry ``` Get the stroke corner geometry of the given design block. * `id:`: The block whose stroke join geometry should be queried. * Returns: The stroke join geometry. ``` let strokeCornerGeometry = try engine.block.getStrokeCornerGeometry(block) ``` Scopes - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-scopes?language=swift&platform=ios#setup # Scopes In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine `block` API to modify a block's scopes. For a more high-level introduction, check the corresponding [guide](/docs/cesdk/engine/guides/scopes/). Scopes describe different aspects of a block, e.g. layout or style and can be enabled or disabled for every single block. There's also the option to control a scope globally. When configuring a scope globally you can set an override to always allow or deny a certain type of manipulation for every block. Or you can configure the global scope to defer to the individual block scopes. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` public func findAllScopes() -> [String] ``` Gets all available global scopes that can be set. * Returns: A list of all available global scopes. ``` /* Store a list of all available global scopes. */ let scopes = try engine.editor.findAllScopes() ``` ``` public func setGlobalScope(key: String, value: GlobalScope) throws ``` Set a scope to be globally allowed, denied, or deferred to the block-level. * `key`: The scope to set. * `value`: `.allow` will always allow the scope, `.deny` will always deny the scope, and `.defer` will defer to the block-level. ``` /* Let the global scope defer to the block-level. */ try engine.editor.setGlobalScope(key: "layer/move", value: .defer) /* Manipulation of layout properties of any block will fail at this point. */ do { try engine.block.setPositionX(image, value: 100) // Not allowed } catch { print(error.localizedDescription) } ``` ``` public func getGlobalScope(key: String) throws -> GlobalScope ``` Query the state of a global scope. * `key:`: The scope to query. * Returns: `.allow` if the scope is allowed, `.deny` if it is disallowed, and `.defer` if it is deferred to the block-level. ``` /* This will return `.defer`. */ try engine.editor.getGlobalScope(key: "layer/move") ``` ``` public func isScopeEnabled(_ id: DesignBlockID, key: String) throws -> Bool ``` Query whether a scope is enabled for a given block. * `id`: The block whose scope state should be queried. * `key`: The scope to query. * Returns: The enabled state of the scope for the given block. ``` /* Verify that the "layer/move" scope is now enabled for the image block. */ try engine.block.isScopeEnabled(image, key: "layer/move") ``` ``` public func setScopeEnabled(_ id: DesignBlockID, key: String, enabled: Bool) throws ``` Enable or disable a scope for a given block. * `id`: The block whose scope should be enabled or disabled. * `key`: The scope to enable or disable. * `enabled`: Whether the scope should be enabled or disabled. ``` /* Allow the user to control the layout properties of the image block. */ try engine.block.setScopeEnabled(image, key: "layer/move", enabled: true) /* Manipulation of layout properties of any block is now allowed. */ do { try engine.block.setPositionX(image, value: 100) // Allowed } catch { print(error.localizedDescription) } ``` ``` public func isAllowedByScope(_ id: DesignBlockID, key: String) throws -> Bool ``` Check if a scope is allowed for a given block. * `id`: The block to check. * `key`: The scope to check. * Returns: Whether the scope is allowed for the given block. ``` /* This will return true as well since the global scope is set to `.defer`. */ try engine.block.isAllowedByScope(image, key: "layer/move") ``` Lifecycle - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-lifecycle?language=swift&platform=ios#setup # Lifecycle In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify scenes through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. Only blocks that are direct or indirect children of a `page` block are rendered. Scenes without any `page` child may not be properly displayed by the CE.SDK editor. ## Functions ``` public func create(_ type: DesignBlockType) throws -> DesignBlockID ``` Create a new block. * `type:`: The type of the block that shall be created. * Returns: The created blocks handle. ``` let block = try engine.block.create(.graphic) ``` By default, the following blocks are available: * `DesignBlockType.page` * `DesignBlockType.graphic` * `DesignBlockType.text` * `DesignBlockType.audio` * `DesignBlockType.cutout` To create a scene, use [`scene.create`](/docs/cesdk/engine/guides/create-scene/) instead. ``` public func saveToString(blocks: [DesignBlockID], allowedResourceSchemes: [String] = ["bundle", "file", "http", "https"]) async throws -> String ``` Saves the given blocks into a string. If given the root of a block hierarchy, e.g. a page with multiple children, the entire hierarchy is saved. * `blocks`: The blocks to save. * `allowedResourceSchemes`: If a resource URL has a scheme that is not in this list an error will be thrown. * Returns: A string representation of the blocks. ``` let savedBlocksString = try await engine.block.saveToString(blocks: [block]) ``` ``` public func saveToArchive(blocks: [DesignBlockID]) async throws -> Blob ``` Saves the given blocks and all of their referenced assets into an archive. The archive contains all assets that were accessible when this function was called. Blocks in the archived scene reference assets relative from to the location of the scene file. These references are resolved when loading such a scene via `scene.load(from url:)`. * `blocks:`: The blocks to save. * Returns: A serialized scene data blob. ``` let savedBlocksArchive = try await engine.block.saveToArchive(blocks: [block]) ``` ``` public func load(from string: String) async throws -> [DesignBlockID] ``` Loads existing blocks from the given string. The blocks are not attached by default and won't be visible until attached to a page or the scene. The UUID of the loaded blocks is replaced with a new one. * `string:`: A string representing the given blocks. * Returns: A list of loaded blocks. ``` let loadedBlocksString = try await engine.block.load(from: savedBlocks) ``` ``` public func loadArchive(from url: URL) async throws -> [DesignBlockID] ``` Loads existing blocks from an archive. The blocks are not attached by default and won't be visible until attached to a page or the scene. The UUID of the loaded blocks is replaced with a new one. * `url:`: The URL of the blocks archive file. * Returns: A list of loaded blocks. ``` let loadedBlocksArchive = try await engine.block.loadArchive(from: .init(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1_blocks.zip")!) ``` ``` public func getType(_ id: DesignBlockID) throws -> String ``` Get the type of the given block, fails if the block is invalid. * `id:`: The block to query. * Returns: The blocks type. ``` let blockType = try engine.block.getType(block) ``` ``` public func setName(_ id: DesignBlockID, name: String) throws ``` Update a block's name. * `id`: The block to update. * `name`: The name to set. ``` try engine.block.setName(block, name: "someName") ``` ``` public func getName(_ id: DesignBlockID) throws -> String ``` Get a block's name. * `id:`: The block to query. * Returns: The block's name. ``` let name = try engine.block.getName(block) ``` ``` public func duplicate(_ id: DesignBlockID) throws -> DesignBlockID ``` Duplicates a block including its children. Required scope: "lifecycle/duplicate" * `id:`: The block to duplicate. * Returns: The handle of the duplicate. ``` let duplicate = try engine.block.duplicate(block) ``` ``` public func destroy(_ id: DesignBlockID) throws ``` Destroys a block. Required scope: "lifecycle/destroy" * `id:`: The block to destroy. ``` try engine.block.destroy(duplicate) ``` ``` public func isValid(_ id: DesignBlockID) -> Bool ``` Check if a block is valid. A block becomes invalid once it has been destroyed. * `id:`: The block to query. * Returns: `true`, if the block is valid. ``` engine.block.isValid(duplicate) // false ``` How to Store Custom Metadata - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/store-metadata?language=swift&platform=ios#setup # How to Store Custom Metadata CE.SDK allows you to store custom metadata in your scenes. You can attach metadata to your scene or directly to your individual design blocks within the scene. This metadata is persistent across saving and loading of scenes. It simply consists of key value pairs of strings. Using any string-based serialization format such as JSON will allow you to store even complex objects. Please note that when duplicating blocks their metadata will also be duplicated. Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-store-metadata/StoreMetadata.swift) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-store-metadata/StoreMetadata.swift) ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. ``` var scene = try await engine.scene.create(fromImage: .init(string: "https://img.ly/static/ubq_samples/imgly_logo.jpg")!) let block = try engine.block.find(byType: .graphic).first! ``` ## Working with Metadata We can add metadata to any design block using `func setMetadata(_ id: DesignBlockID, key: String, value: String) throws`. This also includes the scene block. ``` try engine.block.setMetadata(scene, key: "author", value: "img.ly") try engine.block.setMetadata(block, key: "customer_id", value: "1234567890") /* We can even store complex objects */ struct Payment: Encodable { let id: Int let method: String let received: Bool } let payment = Payment(id: 5, method: "credit_card", received: true) try engine.block.setMetadata( block, key: "payment", value: String(data: JSONEncoder().encode(payment), encoding: .utf8)! ) ``` We can retrieve metadata from any design block or scene using `func getMetadata(_ id: DesignBlockID, key: String) throws`. Before accessing the metadata you check for its existence using `func hasMetadata(_ id: DesignBlockID, key: String) throws -> Bool`. ``` /* This will return "img.ly" */ try engine.block.getMetadata(scene, key: "author") /* This will return "1000000" */ try engine.block.getMetadata(block, key: "customer_id") ``` We can query all metadata keys from any design block or scene using `func findAllMetadata(_ id: DesignBlockID) throws -> [String]`. For blocks without any metadata, this will return an empty list. ``` /* This will return ["customer_id"] */ try engine.block.findAllMetadata(block) ``` If you want to get rid of any metadata, you can use `func removeMetadata(_ id: DesignBlockID, key: String) throws`. ``` try engine.block.removeMetadata(block, key: "payment") /* This will return false */ try engine.block.hasMetadata(block, key: "payment") ``` Metadata will automatically be saved and loaded as part the scene. So you don't have to worry about it getting lost or having to save it separately. ``` /* We save our scene and reload it from scratch */ let sceneString = try await engine.scene.saveToString() scene = try await engine.scene.load(from: sceneString) /* This still returns "img.ly" */ try engine.block.getMetadata(scene, key: "author") /* And this still returns "1234567890" */ try engine.block.getMetadata(block, key: "customer_id") ``` Scene Contents - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/scene-contents?language=swift&platform=ios#setup # Scene Contents Learn how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to explore scene contents through the `scene` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ``` public func getPages() throws -> [DesignBlockID] ``` Get the sorted list of pages in the scene. * Returns: The sorted list of pages in the scene. ``` let pages = try engine.scene.getPages() ``` ``` public func setDesignUnit(_ designUnit: DesignUnit) throws ``` Converts all values of the current scene into the given design unit. * `designUnit:`: The new design unit of the scene. ``` try engine.scene.setDesignUnit(.px) ``` ``` public func getDesignUnit() throws -> DesignUnit ``` Returns the design unit of the current scene. * Returns: The current design unit. ``` /* Now returns DesignUnit.px */ _ = try engine.scene.getDesignUnit() ``` ``` public func getCurrentPage() throws -> DesignBlockID? ``` Get the current page, i.e., the page of the first selected element if this page is at least 25% visible, otherwise, the page nearest to the viewport center. * Returns: The current page in the scene or an error. ``` val currentPage = engine.scene.getCurrentPage() ``` ``` public func findNearestToViewPortCenter(byKind kind: String) throws -> [DesignBlockID] ``` Finds all blocks with the given kind sorted by distance to viewport center. * `kind:`: The kind to search for. * Returns: A list of block ids with the given kind sorted by distance to viewport center. ``` val nearestImageByKind = engine.scene.findNearestToViewPortCenter(byKind: "image").first! ``` ``` public func findNearestToViewPortCenter(byType type: DesignBlockType) throws -> [DesignBlockID] ``` Finds all blocks with the given type sorted by distance to viewport center. * `type:`: The type to search for. * Returns: A list of block ids with the given type sorted by distance to viewport center. ``` val nearestPageByType = engine.scene.findNearestToViewPortCenter(byType: .page).first! ``` Interacting with the Scene using the CreativeEditor SDK Engine - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/blocks?language=swift&platform=ios#creating-or-using-an-existing-scene # Interacting with the Scene using the CreativeEditor SDK Engine A block (or design block) is the main building unit in CE.SDK. Blocks are organized in a hierarchy through parent-child relationships. A scene is a specialized block that acts as the root of this hierarchy. Commonly, a scene contains several pages which in turn contain any other blocks such as images and texts. In this example, we want to show you how to create and modify a scene and its blocks in CE.SDK. Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-modifying-scenes/ModifyingScenes.swift) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-modifying-scenes/ModifyingScenes.swift) ## Creating or Using an Existing Scene When using the Engine's API in the context of the CE.SDK editor, there's already an existing scene. You can obtain a handle to this scene by calling the [SceneAPI](/docs/cesdk/engine/api/scene/)'s `func get() throws -> DesignBlockID?` method. However, when using the Engine on its own you first have to create a scene, e.g. using `func create() throws -> DesignBlockID`. See the [Creating Scenes](/docs/cesdk/engine/guides/create-scene/) guide for more details and options. ``` let scene = try engine.scene.get() /* In engine only mode we have to create our own scene and page. */ if scene == nil { let scene = try engine.scene.create() ``` Next, we need a page to place our blocks on. The scene automatically arranges its pages either in a vertical (the default) or horizontal layout. Again in the context of the editor, there's already an existing page. To fetch that page call the [BlockAPI](/docs/cesdk/engine/api/block/)'s `func find(byType type: DesignBlockType) throws -> [DesignBlockID]` method and use the first element of the returned array. When only using the engine, you have to create a page yourself and append it to the scene. To do that create the page using `func create(_ type: DesignBlockType) throws -> DesignBlockID` and append it to the scene with `func appendChild(to parent: DesignBlockID, child: DesignBlockID) throws`. ``` let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) } /* Find all pages in our scene. */ let pages = try engine.block.find(byType: .page) /* Use the first page we found. */ let page = pages.first! ``` At this point, you should have a handle to an existing scene as well as a handle to its page. Now it gets interesting when we start to add different types of blocks to the scene's page. ## Modifying the Scene As an example, we create a graphic block using the [BlockAPI](/docs/cesdk/engine/api/block/)'s `create()` method which we already used for creating our page. Then we set a rect shape and an image fill to this newly created block to give it a visual representation. To see what other kinds of blocks are available see the [Block Types](/docs/cesdk/engine/api/block-types/) in the API Reference. ``` /* Create a graphic block and add it to the scene's page. */ let block = try engine.block.create(.graphic) let fill = try engine.block.createFill(.image) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setFill(block, fill: fill) ``` We set a property of our newly created image fill by giving it a URL to reference an image file from. We also make sure the entire image stays visible by setting the block's content fill mode to `'Contain'`. To learn more about block properties check out the [Block Properties](/docs/cesdk/engine/api/block-properties/) API Reference. ``` try engine.block.setString( fill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/imgly_logo.jpg" ) /* The content fill mode 'Contain' ensures the entire image is visible. */ try engine.block.setEnum(block, property: "contentFill/mode", value: "Contain") ``` And finally, for our image to be visible we have to add it to our page using `appendChild`. ``` try engine.block.appendChild(to: page, child: block) ``` To frame everything nicely and put it into view we direct the scene's camera to zoom on our page. ``` /* Zoom the scene's camera on our page. */ try await engine.scene.zoom(to: page) ``` Buffers - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/editor-buffers?language=swift&platform=ios#setup # Buffers In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to create buffers through the `editor` API. Buffers can hold arbitrary data. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. #### Limitations Buffers are intended for temporary data only. * Buffer data is not part of the [scene serialization](/docs/cesdk/engine/api/scene-lifecycle/) * Changes to buffers can't be undone using the [history system](/docs/cesdk/engine/api/editor-history/) ``` public func createBuffer() -> URL ``` Create a resizable buffer that can hold arbitrary data. * Returns: A URL to identify the buffer. ``` let audioBuffer = engine.editor.createBuffer() ``` ``` public func destroyBuffer(url: URL) throws ``` Destroy a buffer and free its resources. * `url`: The URL of the buffer to destroy. ``` try engine.editor.destroyBuffer(url: audioBuffer) ``` ``` public func setBufferData(url: URL, offset: UInt, data: Data) throws ``` Set the data of a buffer. * `url`: The URL of the buffer. * `offset`: The offset in bytes at which to start writing. * `data`: The data to write. ``` try engine.editor.setBufferData(url: audioBuffer, offset: 0, data: Data(buffer: buffer)) ``` ``` public func getBufferData(url: URL, offset: UInt, length: UInt) throws -> Data ``` Get the data of a buffer. * `url`: The URL of the buffer. * `offset`: The offset in bytes at which to start reading. * `length`: The number of bytes to read. * Returns: The data read from the buffer or an error. ``` let chunk = try engine.editor.getBufferData(url: audioBuffer, offset: 0, length: 4096) ``` ``` public func setBufferLength(url: URL, length: UInt) throws ``` Set the length of a buffer. * `url`: The URL of the buffer. * `length`: The new length of the buffer in bytes. ``` try engine.editor.setBufferLength(url: audioBuffer,length: UInt(truncating: length) / 2) ``` ``` public func getBufferLength(url: URL) throws -> NSNumber ``` Get the length of a buffer. * `url`: The URL of the buffer. * Returns: The length of the buffer in bytes. ``` let length = try engine.editor.getBufferLength(url: audioBuffer) ``` Utils - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/editor-utils?language=swift&platform=ios#setup # Utils In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/creative-sdk)'s CreativeEngine to control URI lookups and perform color conversions in the `editor` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## URI Resolver The `CreativeEngine` allows overriding how `URI`'s are resolved at runtime using the following APIs. See the [in-depth guide](/docs/cesdk/engine/guides/resolve-custom-uri/) for more details. ``` public func setURIResolver(_ resolver: ((String) -> URL)?) throws ``` Sets a custom URI resolver. This method can be called more than once. Subsequent calls will overwrite previous calls. To remove a previously set resolver, pass the value `nil`. * `resolver:`: Custom resolution function. ``` // Replace all .jpg files with the IMG.LY logo! try engine.editor.setURIResolver { uri in if uri.hasSuffix(".jpg") { return URL(string: "https://img.ly/static/ubq_samples/imgly_logo.jpg")! } // Make use of the default URI resolution behavior. return URL(string: engine.editor.defaultURIResolver(relativePath: uri))! } ``` ``` public func getAbsoluteURI(relativePath: String) throws -> String ``` Resolves the given path. If a custom resolver has been set with `setURIResolver`, it invokes it with the given path. Else, it resolves it as relative to the `basePath` setting. * Important: This performs no validation of whether a file exists at the specified location. * `relativePath:`: A relative path string. * Returns: The resolved absolute uri or an error if an invalid path was given. ``` // The custom resolver will return a path to the IMG.LY logo because the given path ends with ".jpg". // This applies regardless if the given path is relative or absolute. try engine.editor.getAbsoluteURI(relativePath: "/banana.jpg") ``` ``` public func defaultURIResolver(relativePath: String) -> String ``` This is the default implementation for the URI resolver. It resolves the given path relative to the `basePath` setting. * `relativePath:`: The relative path that should be resolved. * Returns: The resolved absolute URI. ``` return URL(string: engine.editor.defaultURIResolver(relativePath: uri))! ``` ## Color Conversions To ease implementing advanced color interfaces, you may rely on the engine to perform color conversions. ``` public func convertColorToColorSpace(color: Color, colorSpace: ColorSpace) throws -> Color ``` Converts a color to the given color space. * `color`: The color to convert. * `colorSpace`: The color space to convert to. * Returns: The converted color. ``` let rgbaGreen = Color(cgColor: CGColor(red: 0, green: 1, blue: 0, alpha: 1))! let cmykGreen = try engine.editor.convertColorToColorSpace(color: rgbaGreen, colorSpace: .cmyk) ``` ## Retrieving the mimetype of a resource ``` public func getMIMEType(url: URL) async throws -> String ``` Returns the mimetype of the resources at the given URL. * Note: If the resource is not already downloaded, this function will download it. * `url:`: The URL of the resource. * Returns: The mimetype of the resource. ``` let mimeType = try await engine.editor.getMIMEType(url: URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.image/images/sample_1.jpg")!) ``` ## Working with resources ``` public func findAllTransientResources() throws -> [(url: URL, size: UInt)] ``` Returns the URLs and sizes of all resources whose data would be lost if the scene was exported. * Note: This function is useful for determining which resources need to be relocated (e.g., to a CDN) before exporting a scene since the resources are not included in the exported scene. * Returns: A list containing the URLs and sizes of transient resources. ``` let transientResources = try engine.editor.findAllTransientResources() ``` ``` public func getResourceData(url: URL, chunkSize: UInt, onData: (Blob) -> Bool) throws ``` Provides the data of a resource at the given URL. * Note: This is a synchronous function and all the chunks are provided immediately before the function returns. * `url`: The URL of the resource. * `chunkSize`: The size of the chunks in which the resource data is provided. * `onData`: The callback function that is called with the resource data. The callback will be called as long as there is data left to provide and the callback returns `true`. ``` let resourceURL = try engine.block.getURL(engine.block.getFill(engine.block.findAllSelected()[0]), property: "fill/image/imageFileURI") try engine.editor.getResourceData(url: resourceURL, chunkSize: 1024) { data in // ... true } ``` ``` public func relocateResource(currentURL: URL, relocatedURL: URL) throws ``` Changes the URL associated with a resource. * Note: This function can be used change the URL of a resource that has been relocated (e.g., to a CDN). * `currentURL`: The current URL of the resource. * `relocatedURL`: The new URL of the resource. ``` try engine.editor.relocateResource(currentURL: resourceURL, relocatedURL: URL(string: "http://your.cdn/image.png")!) ``` How to Edit Videos - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/video?language=swift&platform=ios#setup # How to Edit Videos In addition to static designs, CE.SDK also allows you to create and edit videos. Working with videos introduces the concept of time into the scene, which requires you to switch the scene into the `"Video"` mode. In this mode, each page in the scene has its own separate timeline within which its children can be placed. The `"playback/time"` property of each page controls the progress of time through the page. In order to add videos to your pages, you can add a block with a `FillType.video` fill. As the playback time of the page progresses, the corresponding point in time of the video fill is rendered by the block. You can also customize the video fill's trim in order to control the portion of the video that should be looped while the block is visible. `DesignBlockType.audio` blocks can be added to the page in order to play an audio file during playback. The `playback/timeOffset` property controls after how many seconds the audio should begin to play, while the duration property defines how long the audio should play. The same APIs can be used for other design blocks as well, such as text or graphic blocks. Finally, the whole page can be exported as a video file using the `block.exportVideo` function. Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-video/Video.swift) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-video/Video.swift) ## Setup This example uses the headless Creative Engine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. ## Creating a Video Scene First, we create a scene that is set up for video editing by calling the `scene.createVideo()` API. Then we create a page, add it to the scene and define its dimensions. This page will hold our composition. ``` let scene = try engine.scene.createVideo() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) try engine.block.setWidth(page, value: 1280) try engine.block.setHeight(page, value: 720) ``` ## Setting Page Durations Next, we define the duration of the page using the `func setDuration(_ id: DesignBlockID, duration: Double) throws` API to be 20 seconds long. This will be the total duration of our exported video in the end. ``` try engine.block.setDuration(page, duration: 20) ``` ## Adding Videos In this example, we want to show two videos, one after the other. For this, we first create two graphic blocks and assign two `'video'` fills to them. ``` let video1 = try engine.block.create(.graphic) try engine.block.setShape(video1, shape: engine.block.createShape(.rect)) let videoFill = try engine.block.createFill(.video) try engine.block.setString( videoFill, property: "fill/video/fileURI", // swiftlint:disable:next line_length value: "https://cdn.img.ly/assets/demo/v1/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4" ) try engine.block.setFill(video1, fill: videoFill) let video2 = try engine.block.create(.graphic) try engine.block.setShape(video2, shape: engine.block.createShape(.rect)) let videoFill2 = try engine.block.createFill(.video) try engine.block.setString( videoFill2, property: "fill/video/fileURI", value: "https://cdn.img.ly/assets/demo/v2/ly.img.video/videos/pexels-kampus-production-8154913.mp4" ) try engine.block.setFill(video2, fill: videoFill2) ``` ## Creating a Track While we could add the two blocks directly to the page and and manually set their sizes and time offsets, we can alternatively also use the `track` block to simplify this work. A `track` automatically adjusts the time offsets of its children to make sure that they play one after another without any gaps, based on each child's duration. Tracks themselves cannot be selected directly by clicking on the canvas, nor do they have any visual representation. We create a `track` block, add it to the page and add both videos in the order in which they should play as the track's children. Next, we use the `fillParent` API, which will resize all children of the track to the same dimensions as the page. The dimensions of a `track` are always derived from the dimensions of its children, so you should not call the `setWidth` or `setHeight` APIs on a track, but on its children instead if you can't use the `fillParent` API. ``` let track = try engine.block.create(.track) try engine.block.appendChild(to: page, child: track) try engine.block.appendChild(to: track, child: video1) try engine.block.appendChild(to: track, child: video2) try engine.block.fillParent(track) ``` By default, each block has a duration of 5 seconds after it is created. If we want to show it on the page for a different amount of time, we can use the `setDuration` API. Note that we can just increase the duration of the first video block to 15 seconds without having to adjust anything about the second video. The `track` takes care of that for us automatically so that the second video starts playing after 15 seconds. ``` try engine.block.setDuration(video1, duration: 15) ``` If the video is longer than the duration of the graphic block that it's attached to, it will cut off once the duration of the graphic is reached. If it is too short, the video will automatically loop for as long as its graphic block is visible. We can also manually define the portion of our video that should loop within the graphic using the `func setTrimOffset(_ id: DesignBlockID, offset: Double) throws` and `func setTrimLength(_ id: DesignBlockID, length: Double) throws` APIs. We use the trim offset to cut away the first second of the video and the trim length to only play 10 seconds of the video. Since our graphic is 15 seconds long, the trimmed video will be played fully once and then start looping for the remaining 5 seconds. ``` // Make sure that the video is loaded before calling the trim APIs. try await engine.block.forceLoadAVResource(videoFill) try engine.block.setTrimOffset(videoFill, offset: 1) try engine.block.setTrimLength(videoFill, length: 10) ``` We can control if a video will loop back to its beginning by calling `func setLooping(_ id: DesignBlockID, looping: Bool) throws`. Otherwise, the video will simply hold its last frame instead and audio will stop playing. Looping behavior is activated for all blocks by default. ``` try engine.block.setLooping(videoFill, looping: true) ``` ## Audio If the video of a video fill contains an audio track, that audio will play automatically by default when the video is playing. We can mute it by calling `func setMuted(_ id: DesignBlockID, muted: Bool) throws`. ``` try engine.block.setMuted(videoFill, muted: true) ``` We can also add audio-only files to play together with the contents of the page by adding an `'audio'` block to the page and assigning it the URL of the audio file. ``` let audio = try engine.block.create(.audio) try engine.block.appendChild(to: page, child: audio) try engine.block.setString( audio, property: "audio/fileURI", value: "https://cdn.img.ly/assets/demo/v1/ly.img.audio/audios/far_from_home.m4a" ) ``` We can adjust the volume level of any audio block or video fill by calling `func setVolume(_ id: DesignBlockID, volume: Float) throws`. The volume is given as a fraction in the range of 0 to 1. ``` // Set the volume level to 70%. try engine.block.setVolume(audio, volume: 0.7) ``` By default, our audio block will start playing at the very beginning of the page. We can change this by specifying how many seconds into the scene it should begin to play using the `func setTimeOffset(_ id: DesignBlockID, offset: Double) throws` API. ``` // Start the audio after two seconds of playback. try engine.block.setTimeOffset(audio, offset: 2) ``` By default, our audio block will have a duration of 5 seconds. We can change this by specifying its duration in seconds by using the `func setDuration(_ id: DesignBlockID, duration: Double) throws` API. ``` // Give the Audio block a duration of 7 seconds. try engine.block.setDuration(audio, duration: 7) ``` ## Exporting Video You can start exporting the entire page as a video file by calling `func exportVideo(_ id: DesignBlockID, mimeType: MIMEType)`. The encoding process will run in the background. You can get notified about the progress of the encoding process by the `async` stream that's returned. Since the encoding process runs in the background the engine will stay interactive. So, you can continue to use the engine to manipulate the scene. Please note that these changes won't be visible in the exported video file because the scene's state has been frozen at the start of the export. ``` // Export page as mp4 video. let mimeType: MIMEType = .mp4 let exportTask = Task { for try await export in try await engine.block.exportVideo(page, mimeType: mimeType) { switch export { case let .progress(renderedFrames, encodedFrames, totalFrames): print("Rendered", renderedFrames, "frames and encoded", encodedFrames, "frames out of", totalFrames) case let .finished(video: videoData): return videoData } } return Blob() } let blob = try await exportTask.value ``` Save Scenes to a Blob - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/save-scene-to-blob?language=swift&platform=ios#warning # Save Scenes to a Blob In this example, we will show you how to save scenes as a `Blob` with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-save-scene-to-blob/SaveSceneToBlob.swift) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-save-scene-to-blob/SaveSceneToBlob.swift) The CreativeEngine allows you to save scenes in a binary format to share them between editors or store them for later editing. This is done by converting the contents of a scene to a string, which can then be stored or transferred. For sending these to a remote location, we wrap them in a `Blob` and treat it as a file object. #### Warning A _scene file_ does not include any fonts or images. Only the source URIs of assets, the general layout, and element properties are stored. When loading scenes in a new environment, ensure previously used asset URIs are available. To get hold of the scene contents as string, you need to use `engine.scene.saveToString()`. This is an asynchronous method. After waiting for the coroutine to finish, we receive a plain string holding the entire scene currently loaded in the editor. This includes all pages and any hidden elements but none of the actual asset data. ``` let savedSceneString = try await engine.scene.saveToString() ``` The returned string consists solely of ASCII characters and can safely be used further or written to a database. ``` let blob = savedSceneString.data(using: .utf8)! ``` That object can then be treated as a form file parameter and sent to a remote location. ``` var request = URLRequest(url: .init(string: "https://example.com/upload/")!) request.httpMethod = "POST" let (data, response) = try await URLSession.shared.upload(for: request, from: blob) ``` How to Use Effects & Filters - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/using-effects?language=swift&platform=ios#setup # How to Use Effects & Filters Some [design blocks](/docs/cesdk/engine/guides/blocks/) in CE.SDK such as pages and graphic blocks allow you to add effects to them. An effect can modify the visual output of a block's [fill](/docs/cesdk/engine/guides/using-fills/). CreativeEditor SDK supports many different types of effects, such as adjustments, LUT filters, pixelization, glow, vignette and more. Similarly to blocks, each effect instance has a numeric id which can be used to query and [modify its properties](/docs/cesdk/engine/api/block-properties/). Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-using-effects/UsingEffects.swift) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-using-effects/UsingEffects.swift) ## Setup This example uses the headless Creative Engine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. We create a scene containing a graphic block with an image fill and want to apply effects to this image. ``` let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(block, value: 100) try engine.block.setPositionY(block, value: 50) try engine.block.setWidth(block, value: 300) try engine.block.setHeight(block, value: 300) try engine.block.appendChild(to: page, child: block) let fill = try engine.block.createFill(.image) try engine.block.setString( fill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg" ) try engine.block.setFill(block, fill: fill) ``` ## Accessing Effects Not all types of design blocks support effects, so you should always first call the `func supportsEffects(_ id: DesignBlockID) throws -> Bool` API before accessing any of the following APIs. ``` try engine.block.supportsEffects(scene) // Returns false try engine.block.supportsEffects(block) // Returns true ``` ## Creating an Effect In order to add effects to our block, we first have to create a new effect instance, which we can do by calling `func createEffect(_ type: EffectType) throws -> DesignBlockID` and passing it the type of effect that we want. In this example, we create a pixelization and an adjustment effect. Please refer to [the API docs](/docs/cesdk/engine/api/block-effects/) for a complete list of supported effect types. ``` let pixelize = try engine.block.createEffect(.pixelize) let adjustments = try engine.block.createEffect(.adjustments) ``` ## Adding Effects Now we have two effects but the output of our scene looks exactly the same as before. That is because we still need to append these effects to the graphic design block's list of effects, which we can do by calling `func appendEffect(_ id: DesignBlockID, effectID: DesignBlockID) throws`. We can also insert or remove effects from specific indices of a block's effect list using the `func insertEffect(_ id: DesignBlockID, effectID: DesignBlockID, index: Int) throws` and `func removeEffect(_ id: DesignBlockID, index: Int) throws` APIs. Effects will be applied to the block in the order they are placed in the block's effects list. If the same effect appears multiple times in the list, it will also be applied multiple times. In our case, the adjustments effect will be applied to the image first, before the result of that is then pixelated. ``` try engine.block.appendEffect(block, effectID: pixelize) try engine.block.insertEffect(block, effectID: adjustments, index: 0) // try engine.block.removeEffect(rect, index: 0) ``` ## Querying Effects Use the `func getEffects(_ id: DesignBlockID) throws -> [DesignBlockID]` API to query the ordered list of effect ids of a block. ``` // This will return [adjustments, pixelize] let effectsList = try engine.block.getEffects(block) ``` ## Destroying Effects If we created an effect that we don't want anymore, we have to make sure to destroy it using the same `func destroy(_ id: DesignBlockID) throws` API that we also call for design blocks. Effects that are attached to a design block will be automatically destroyed when the design block is destroyed. ``` let unusedEffect = try engine.block.createEffect(.halfTone) try engine.block.destroy(unusedEffect) ``` ## Effect Properties Just like design blocks, effects with different types have different properties that you can query and modify via the API. Use `func findAllProperties(_ id: DesignBlockID) throws -> [String]` in order to get a list of all properties of a given effect. Please refer to the [API docs](/docs/cesdk/engine/api/block-effects/) for a complete list of all available properties for each type of effect. ``` let allPixelizeProperties = try engine.block.findAllProperties(pixelize) let allAdjustmentProperties = try engine.block.findAllProperties(adjustments) ``` Once we know the property keys of an effect, we can use the same APIs as for design blocks in order to [modify those properties](/docs/cesdk/engine/api/block-properties/). Our adjustment effect here for example will not modify the output unless we at least change one of its adjustment properties - such as the brightness - to not be zero. ``` try engine.block.setInt(pixelize, property: "pixelize/horizontalPixelSize", value: 20) try engine.block.setFloat(adjustments, property: "effect/adjustments/brightness", value: 0.2) ``` ## Disabling Effects You can temporarily disable and enable the individual effects using the `func setEffectEnabled(effectID: DesignBlockID, enabled: Bool) throws` API. When the effects are applied to a block, all disabled effects are simply skipped. Whether an effect is currently enabled or disabled can be queried with `func isEffectEnabled(effectID: DesignBlockID) throws -> Bool`. ``` try engine.block.setEffectEnabled(effectID: pixelize, enabled: false) try engine.block.setEffectEnabled(effectID: pixelize, enabled: !engine.block.isEffectEnabled(effectID: pixelize)) ``` Configure Color Palette - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/mobile-editor/configuration/color-palette/?platform=ios&language=swift # Configure Color Palette In this example, we will show you how to make color palette configurations for the mobile editor. The example is based on the `Design Editor`, however, it is exactly the same for all the other [solutions](/docs/cesdk/mobile-editor/solutions/). Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-configuration-color-palette/ColorPaletteEditorSolution.swift). * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-configuration-color-palette/ColorPaletteEditorSolution.swift) ## Modifiers After initializing an editor SwiftUI view you can apply any SwiftUI _modifier_ to customize it like for any other SwiftUI view. All public Swift `extension`s of existing types provided by IMG.LY, e.g., for the SwiftUI `View` protocol or for the `CGColor` class, are exposed in a separate `.imgly` property namespace. The color palette configuration to customize the editor is no exception to this rule and is implemented as a SwiftUI _modifier_. ``` DesignEditor(settings) ``` * `colorPalette` - the color palette used for UI elements that contain predefined color options, e.g., for "Fill Color" or "Stroke Color". It expects an array of `NamedColor`s that are composed of a name, required for accessibility, and the actual `CGColor` to use. It should contain seven elements. Six of them are always shown. The seventh is only shown when a color property does not support a disabled state. This example shows the default configuration. ``` .imgly.colorPalette([ .init("Blue", .imgly.blue), .init("Green", .imgly.green), .init("Yellow", .imgly.yellow), .init("Red", .imgly.red), .init("Black", .imgly.black), .init("White", .imgly.white), .init("Gray", .imgly.gray), ]) ``` Reading and modifying text properties in the Headless CreativeEngine - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/text-properties?language=swift&platform=ios#editing-the-text-string # Reading and modifying text properties in the Headless CreativeEngine In this example, we want to show how to read and modify the text block's contents via the API in the CreativeEngine. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-text-properties?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Text+Properties&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-text-properties). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-text-properties?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Text+Properties&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-text-properties) ## 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. ``` try engine.block.replaceText(text, text: "Hello World") ``` When specifying an empty range, the new text is inserted at its lower bound. ``` // 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. ``` // 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. ``` // 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 ``` 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. ``` 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). ``` 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. ``` 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. ``` 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). ``` 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`: ``` 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. ``` 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`. ``` 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. ``` 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. ``` 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. ``` let typeface = Typeface( name: "Roboto", fonts: [ Font( uri: URL(string: "https://cdn.img.ly/assets/v3/ly.img.typeface/fonts/Roboto/Roboto-Bold.ttf")!, subFamily: "Bold", weight: .bold, style: .normal ), Font( uri: URL(string: "https://cdn.img.ly/assets/v3/ly.img.typeface/fonts/Roboto/Roboto-BoldItalic.ttf")!, subFamily: "Bold Italic", weight: .bold, style: .italic ), Font( uri: URL(string: "https://cdn.img.ly/assets/v3/ly.img.typeface/fonts/Roboto/Roboto-Italic.ttf")!, subFamily: "Italic", weight: .normal, style: .italic ), Font( uri: URL(string: "https://cdn.img.ly/assets/v3/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. ``` 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. ``` let currentDefaultTypeface = try engine.block.getTypeface(text) ``` ## Font Sizes Text blocks can have multiple ranges with different font sizes. The `func setTextFontSize(_ id: DesignBlockID, fontSize: Float, in subrange: Range? = nil) throws` 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]`. The `func getTextFontSizes(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [Float]` API returns an ordered list of unique font sizes in the requested range. ## 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. ``` 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. ``` 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. 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]`. ``` 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. 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]`. ``` let fontStyles = try engine.block.getTextFontStyles(text) ``` Apply a Template to a Scene - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/scene-apply-template?language=swift&platform=ios#setup # Apply a Template to a 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. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Applying Template Scenes ``` 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. ``` try await engine.scene.applyTemplate(from: "UBQ1ewoiZm9ybWF0Ij...") ``` ``` 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. ``` try await engine.scene ``` Exporting to PDF with an underlayer - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/underlayer?language=swift&platform=ios#warning # Exporting to PDF with an underlayer When printing on a non-white medium or on a special medium like fabric or glass, printing your design over an underlayer helps achieve the desired result. An underlayer will typically be printed using a special ink and be of the exact shape of your design. When exporting to PDF, you can specify that an underlayer be automatically generated in the `ExportOptions`. An underlayer will be generated by detecting the contour of all elements on a page and inserting a new block with the shape of the detected contour. This new block will be positioned behind all existing block. After exporting, the new block will be removed. The result will be a PDF file containing an additional shape of the same shape as your design and sitting behind it. The ink to be used by the printer is specified in the `ExportOptions` with a [spot color](/docs/cesdk/engine/guides/colors/). You can also adjust the scale of the underlayer shape with a negative or positive offset, in design units. #### Warning Do not flatten the resulting PDF file or you will lose the underlayer shape which sits behind your design. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-underlayer?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Underlayer&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-underlayer). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-underlayer?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Underlayer&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-underlayer) ## Setup the scene We first create a new scene with a graphic block that has a color fill. ``` let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.star)) try engine.block.setPositionX(block, value: 350) try engine.block.setPositionY(block, value: 400) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 100) let fill = try engine.block.createFill(.color) try engine.block.setFill(block, fill: fill) let rgbaBlue = Color.rgba(r: 0, g: 0, b: 1, a: 1) try engine.block.setColor(fill, property: "fill/color/value", color: rgbaBlue) ``` ## Add the underlayer's spot color Here we instantiate a spot color with the known name of the ink the printer should use for the underlayer. The visual color approximation is not so important, so long as the name matches what the printer expects. ``` engine.editor.setSpotColor(name: "RDG_WHITE", r: 0.8, g: 0.8, b: 0.8) ``` ## Exporting with an underlayer We enable the automatic generation of an underlayer on export with the option `exportPdfWithUnderlayer = true`. We specify the ink to use with `underlayerSpotColorName = 'RDG_WHITE'`. In this instance, we make the underlayer a bit smaller than our design so we specify an offset of 2 design units (e.g. millimeters) with `underlayerOffset = -2.0`. ``` let mimeTypePdf: MIMEType = .pdf let options = ExportOptions(exportPdfWithUnderlayer: true, underlayerSpotColorName: "RDG_WHITE", underlayerOffset: -2.0) let blob = try await engine.block.export(page, mimeType: mimeTypePdf, options: options) ``` Metadata - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-metadata?language=swift&platform=ios#setup # Metadata In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine `block` API to modify a block's metadata. Metadata are a flat map of strings, that you can freely modify. They're serialized with the scene and may be used for various applications. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` public func setMetadata(_ id: DesignBlockID, key: String, value: String) throws ``` Set a metadata value of a block identified by a key. If the key does not exist, yet, it will be added. * `id`: The block whose metadata will be accessed. * `key`: The key used to identify the desired piece of metadata. * `value`: The value to set. ``` image, try engine.block.setMetadata( key: "payment", value: String(data: JSONEncoder().encode(payment), encoding: .utf8)! ) ``` ``` public func hasMetadata(_ id: DesignBlockID, key: String) throws -> Bool ``` Check if the block has metadata associated with the key. * `id`: The block whose metadata will be accessed. * `key`: The key used to identify the desired piece of metadata. * Returns: Whether the key exists. ``` /* This will return true */ try engine.block.hasMetadata(scene, key: "author") ``` ``` public func getMetadata(_ id: DesignBlockID, key: String) throws -> String ``` Get a metadata value of a block identified by a key. If the key does not exist, yet, this method will fail. * `id`: The block whose metadata will be accessed. * `key`: The key used to identify the desired piece of metadata. * Returns: The value associated with the key. ``` /* This will return "img.ly" */ try engine.block.getMetadata(scene, key: "author") /* This will return "1000000" */ try engine.block.getMetadata(image, key: "customer_id") ``` ``` public func findAllMetadata(_ id: DesignBlockID) throws -> [String] ``` Query all metadata keys that exist on this block. * `id:`: The block whose metadata will be accessed. * Returns: A list of all metadata keys on this block or an error, if the block is invalid. ``` /* This will return ["customer_id"] */ try engine.block.findAllMetadata(image) ``` ``` public func removeMetadata(_ id: DesignBlockID, key: String) throws ``` Remove metadata associated with the key from the given block. * `id`: The block whose metadata will be accessed. * `key`: The key used to identify the desired piece of metadata. ``` try engine.block.removeMetadata(image, key: "payment") /* This will return false */ try engine.block.hasMetadata(image, key: "payment") ``` Drop Shadow - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-drop-shadow?language=swift&platform=ios#setup # Drop Shadow In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify an block's drop shadow through the `block` API. Drop shadows can be added to any shape, text or image. One can adjust its offset relative to its block on the X and Y axes, its blur factor on the X and Y axes and whether it is visible behind a transparent block. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` public func supportsDropShadow(_ id: DesignBlockID) throws -> Bool ``` Query if the given block has a drop shadow property. * `id:`: The block to query. * Returns: `true` if the block has a drop shadow property. ``` if try engine.block.supportsDropShadow(block) { ``` ``` public func setDropShadowEnabled(_ id: DesignBlockID, enabled: Bool) throws ``` Enable or disable the drop shadow of the given design block. Required scope: "appearance/shadow" * `id`: The block whose drop shadow should be enabled or disabled. * `enabled`: If `true`, the drop shadow will be enabled. ``` try engine.block.setDropShadowEnabled(block, enabled: true) ``` ``` public func isDropShadowEnabled(_ id: DesignBlockID) throws -> Bool ``` Query if the drop shadow of the given design block is enabled. * `id:`: The block whose drop shadow state should be queried. * Returns: `true` if the block's drop shadow is enabled. ``` let dropShadowIsEnabled = try engine.block.isDropShadowEnabled(block) ``` ``` public func setDropShadowColor(_ id: DesignBlockID, color: Color) throws ``` Set the drop shadow color of the given design block. Required scope: "appearance/shadow" * `id`: The block whose drop shadow color should be set. * `color`: The color to set. ``` try engine.block.setDropShadowColor(block, color: .rgba(r: 1.0, g: 0.75, b: 0.8, a: 1.0)) ``` ``` public func getDropShadowColor(_ id: DesignBlockID) throws -> Color ``` Get the drop shadow color of the given design block. * `id:`: The block whose drop shadow color should be queried. * Returns: The drop shadow color. ``` let dropShadowColor = try engine.block.getDropShadowColor(block) ``` ``` public func setDropShadowOffsetX(_ id: DesignBlockID, offsetX: Float) throws ``` Set the drop shadow's X offset of the given design block. Required scope: "appearance/shadow" * `id`: The block whose drop shadow's X offset should be set. * `offsetX`: The X offset to be set. ``` try engine.block.setDropShadowOffsetX(block, offsetX: -10) ``` ``` public func setDropShadowOffsetY(_ id: DesignBlockID, offsetY: Float) throws ``` Set the drop shadow's Y offset of the given design block. Required scope: "appearance/shadow" * `id`: The block whose drop shadow's Y offset should be set. * `offsetY`: The Y offset to be set. ``` try engine.block.setDropShadowOffsetY(block, offsetY: 5) ``` ``` public func getDropShadowOffsetX(_ id: DesignBlockID) throws -> Float ``` Get the drop shadow's X offset of the given design block. * `id:`: The block whose drop shadow's X offset should be queried. * Returns: The offset. ``` let dropShadowOffsetX = try engine.block.getDropShadowOffsetX(block) ``` ``` public func getDropShadowOffsetY(_ id: DesignBlockID) throws -> Float ``` Get the drop shadow's Y offset of the given design block. * `id:`: The block whose drop shadow's Y offset should be queried. * Returns: The offset. ``` let dropShadowOffsetX = try engine.block.getDropShadowOffsetY(block) ``` ``` public func setDropShadowBlurRadiusX(_ id: DesignBlockID, blurRadiusX: Float) throws ``` Set the drop shadow's blur radius on the X axis of the given design block. Required scope: "appearance/shadow" * `id`: The block whose drop shadow's blur radius should be set. * `blurRadiusX`: The blur radius to be set. ``` try engine.block.setDropShadowBlurRadiusX(block, blurRadiusX: -10) ``` ``` public func setDropShadowBlurRadiusY(_ id: DesignBlockID, blurRadiusY: Float) throws ``` Set the drop shadow's blur radius on the Y axis of the given design block. Required scope: "appearance/shadow" * `id`: The block whose drop shadow's blur radius should be set. * `blurRadiusY`: The blur radius to be set. ``` try engine.block.setDropShadowBlurRadiusY(block, blurRadiusY: 5) ``` ``` public func setDropShadowClip(_ id: DesignBlockID, clip: Bool) throws ``` Set the drop shadow's clipping of the given design block. (Only applies to shapes.) Required scope: "appearance/shadow" * `id`: The block whose drop shadow's clip should be set. * `clip`: The drop shadow's clip to be set. ``` try engine.block.setDropShadowClip(block, clip: false) ``` ``` public func getDropShadowClip(_ id: DesignBlockID) throws -> Bool ``` Get the drop shadow's clipping of the given design block. * `id:`: The block whose drop shadow's clipping should be queried. * Returns: The drop shadow's clipping. ``` let dropShadowClip = try getDropShadowClip(block) ``` ``` public func getDropShadowBlurRadiusX(_ id: DesignBlockID) throws -> Float ``` Get the drop shadow's blur radius on the X axis of the given design block. * `id:`: The block whose drop shadow's blur radius should be queried. * Returns: The blur radius. ``` let dropShadowBlurRadiusX = try engine.block.getDropShadowBlurRadiusX(block) ``` ``` public func getDropShadowBlurRadiusY(_ id: DesignBlockID) throws -> Float ``` Get the drop shadow's blur radius on the Y axis of the given design block. * `id:`: The block whose drop shadow's blur radius should be queried. * Returns: The blur radius. ``` let dropShadowBlurRadiusY = try engine.block.getDropShadowBlurRadiusY(block) ``` How to Use the Camera - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/using-camera?language=swift&platform=ios#setup # How to Use the Camera Other than having pre-recorded [video](/docs/cesdk/engine/guides/video/) in your scene you can also have a live preview from a camera in the engine. This allows you to make full use of the engine's capabilities such as [effects](/docs/cesdk/engine/api/block-effects/), [strokes](/docs/cesdk/engine/api/block-strokes/) and [drop shadows](/docs/cesdk/engine/api/block-drop-shadow/), while the preview integrates with the composition of your scene. Simply swap out the `VideoFill` of a block with a `PixelStreamFill`. This guide shows you how the `PixelStreamFill` can be used in combination with a camera. Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-using-camera) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-using-camera) ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. We create a video scene with a single page. Then we create a `PixelStreamFill` and assign it to the page. To demonstrate the live preview capabilities of the engine we also apply an effect to the page. ``` let scene = try engine.scene.createVideo() let stack = try engine.block.find(byType: .stack).first! let page = try engine.block.create(.page) try engine.block.appendChild(to: stack, child: page) let pixelStreamFill = try engine.block.createFill(.pixelStream) try engine.block.setFill(page, fill: pixelStreamFill) try engine.block.appendEffect(page, effectID: try engine.block.createEffect(.halfTone)) ``` ## Orientation To not waste expensive compute time by transforming the pixel data of the buffer itself, it's often beneficial to apply a transformation during rendering and let the GPU handle this work much more efficiently. For this purpose the `PixelStreamFill` has an `orientation` property. You can use it to mirror the image or rotate it in 90° steps. This property lets you easily mirror an image from a front facing camera or rotate the image by 90° when the user holds a device sideways. ``` try engine.block.setEnum( pixelStreamFill, property: "fill/pixelStream/orientation", value: "UpMirrored" ) ``` ## Camera We use the `Camera` helper class that internally creates an `AVCaptureSession` and connects it with audio/video inputs and frame and file outputs. We bring the page fully into view using `engine.scene.zoom`. By calling `camera.captureVideo()` we simultaneously start the frame output and file recording. We can then switch on the `.frame` event and the `.videoCaptured` event. Once the recording is finished we swap the `PixelStreamFill` with a `VideoFill` to play back the recorded video file. ``` let camera = try Camera() Task { try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) for try await event in camera.captureVideo() { ``` ## Updating the Fill In the `.frame` event we update the `PixelStreamFill` with the pixel buffer of the new video frame using `setNativePixelBuffer`. `setNativePixelBuffer` accepts a `CVPixelBuffer`. ``` case let .frame(buffer): try engine.block.setNativePixelBuffer(pixelStreamFill, buffer: buffer) ``` Integrate a Custom Asset Source - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/integrate-a-custom-asset-source?language=swift&platform=ios#local-asset-sources # Integrate a Custom Asset Source In this example, we will show you how to integrate your custom asset sources into [CE.SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-custom-asset-source/CustomAssetSource.swift) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-custom-asset-source/CustomAssetSource.swift) With CE.SDK you can directly add external image providers like Unsplash or your own backend. A third option we will explore in this guide is using the engine's [Asset API](/docs/cesdk/engine/api/assets/) directly. Follow along with this example while we are going to add the Unsplash library. Adding an asset source is done creating an asset source definition and adding it using `func addSource(_ source: AssetSource) throws`. The asset source needs a unique identifier as part of an object implementing the interface of the source. All Asset API methods require the asset source's unique identifier. ``` let source = UnsplashAssetSource(host: secrets.unsplashHost) try engine.asset.addSource(source) ``` The most important function to implement is `func findAssets(sourceID: String, query: AssetQueryData) async throws -> AssetQueryResult`. With this function alone you can define the complete asset source. It receives the asset query as an argument and returns a promise with the results. * The argument is the `queryData` and describes the slice of data the engine wants to use. This includes a query string and pagination information. * The result of this query, besides the actual asset data, returns information like the current page, the next page and the total number of assets available for this specific query. Providing an `async` function gives us great flexibility since we are completely agnostic of how we want to get the assets. We can use `URLSession`, local storage, cache or import a 3rd party library to return the result. ``` let list = try await engine.asset.findAssets( sourceID: "ly.img.asset.source.unsplash", query: .init(query: "", page: 1, perPage: 10) ) ``` Let us implement an Unsplash asset source. Please note that this is just for demonstration purposes only and may not be ideal if you want to integrate Unsplash in your production environment. We will create a class integrating two Unsplash REST endpoints. The setup part only contains endpoint definition, as well as JSON decoder. According to their documentation and guidelines, we have to create an access key and use a proxy to query the API, but this is out of scope for this example. Take a look at Unsplash's documentation for further details. ``` public final class UnsplashAssetSource: NSObject { private lazy var decoder: JSONDecoder = { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase return decoder }() private let host: String private let path: String public init(host: String, path: String = "/unsplashProxy") { self.host = host self.path = path } private struct Endpoint { let path: String let query: [URLQueryItem] static func search(queryData: AssetQueryData) -> Self { Endpoint( path: "/search/photos", query: [ .init(name: "query", value: queryData.query), .init(name: "page", value: String(queryData.page + 1)), .init(name: "per_page", value: String(queryData.perPage)), .init(name: "content_filter", value: "high"), ] ) } static func list(queryData: AssetQueryData) -> Self { Endpoint( path: "/photos", query: [ .init(name: "order_by", value: "popular"), .init(name: "page", value: String(queryData.page + 1)), .init(name: "per_page", value: String(queryData.perPage)), .init(name: "content_filter", value: "high"), ] ) } func url(with host: String, path: String) -> URL? { var components = URLComponents() components.scheme = "https" components.host = host components.path = path + self.path components.queryItems = query return components.url } } } ``` Unsplash has different API endpoints for different use cases. If we want to search we need to call a different endpoint as if we just want to display images without any search term. Therefore we need to check if the query data contains a `query` string. If `findAssets` was called with a non-empty `query` we can call the `/search` endpoint. As we can see in the example, we are passing the `queryData` to this method, containing the following fields: * `queryData.query`: The current search string from the search bar in the asset library. * `queryData.page`: For Unsplash specifically the requested page number starts with 1. We do not query all assets at once but by pages. As the user scrolls down more pages will be requested by calls to the `findAssets` method. * `queryData.perPage`: Determines how many assets we want to have included per page. This might change between calls. For instance, `perPage` can be called with a small number to display a small preview, but with a higher number e.g. if we want to show more assets in a grid view. ``` let endpoint: Endpoint = queryData.query? .isEmpty ?? true ? .list(queryData: queryData) : .search(queryData: queryData) ``` Once we receive the response and check for success we need to map Unsplash's result to what the asset source API needs as a result. The CE.SDK expects an object with the following properties: * `assets`: An array of assets for the current query. We will take a look at what these have to look like in the next paragraph. * `total`: The total number of assets available for the current query. If we search for "Cat" with `perPage` set to 30, we will get 30 assets, but `total` likely will be a much higher number. * `currentPage`: Return the current page that was requested. * `nextPage`: This is the next page that can be requested after the current one. Should be `undefined` if there is no other page (no more assets). In this case we stop querying for more even if the user has scrolled to the bottom. ``` if queryData.query?.isEmpty ?? true { let response = try decoder.decode(UnsplashListResponse.self, from: data) let nextPage = queryData.page + 1 return .init( assets: response.map(AssetResult.init), currentPage: queryData.page, nextPage: nextPage, total: -1 ) } else { let response = try decoder.decode(UnsplashSearchResponse.self, from: data) let (results, total, totalPages) = (response.results, response.total ?? 0, response.totalPages ?? 0) let nextPage = (queryData.page + 1) == totalPages ? -1 : queryData.page + 1 return .init( assets: results.map(AssetResult.init), currentPage: queryData.page, nextPage: nextPage, total: total ) } ``` Every image we get as a result of Unsplash needs to be translated into an object that is expected by the asset source API. We will describe every mandatory and optional property in the following paragraphs. ``` convenience init(image: UnsplashImage) { self.init( id: image.id, locale: "en", label: image.description ?? image.altDescription, tags: image.tags?.compactMap(\.title), meta: [ "uri": image.urls.full.absoluteString, "thumbUri": image.urls.thumb.absoluteString, "blockType": DesignBlockType.graphic.rawValue, "fillType": FillType.image.rawValue, "shapeType": ShapeType.rect.rawValue, "kind": "image", "width": String(image.width), "height": String(image.height), ], context: .init(sourceID: "unsplash"), credits: .init(name: image.user.name!, url: image.user.links?.html), utm: .init(source: "CE.SDK Demo", medium: "referral") ) } ``` `id`: The id of the asset (mandatory). This has to be unique for this source configuration. ``` id: image.id, ``` `locale` (optional): The language locale for this asset is used in `label` and `tags`. ``` locale: "en", ``` `label` (optional): The label of this asset. It could be displayed in the tooltip as well as in the credits of the asset. ``` label: image.description ?? image.altDescription, ``` `tags` (optional): The tags of this asset. It could be displayed in the credits of the asset. ``` tags: image.tags?.compactMap(\.title), ``` `meta`: The meta object stores asset properties that depend on the specific asset type. ``` meta: [ "uri": image.urls.full.absoluteString, "thumbUri": image.urls.thumb.absoluteString, "blockType": DesignBlockType.graphic.rawValue, "fillType": FillType.image.rawValue, "shapeType": ShapeType.rect.rawValue, "kind": "image", "width": String(image.width), "height": String(image.height), ], ``` `uri`: For an image asset this is the URL to the image file that will be used to add the image to the scene. Note that we have to use the Unsplash API to obtain a usable URL at first. ``` "uri": image.urls.full.absoluteString, ``` `thumbUri`: The URI of the asset's thumbnail. It could be used in an asset library. ``` "thumbUri": image.urls.thumb.absoluteString, ``` `blockType`: The type id of the design block that should be created when this asset is applied to the scene. If omitted, CE.SDK will try to infer the block type from an optionally provided `mimeType` property (e.g. `image/jpeg`) or by loading the asset data behind `uri` and parsing the mime type from that. However, this will cause a delay before the asset can be added to the scene, which is why it is always recommended to specify the `blockType` upfront. ``` "blockType": DesignBlockType.graphic.rawValue, ``` `fillType`: The type id of the fill that should be attached to the block when this asset is applied to the scene. If omitted, CE.SDK will default to a solid color fill `//ly.img.ubq/fill/color`. ``` "fillType": FillType.image.rawValue, ``` `shapeType`: The type id of the shape that should be attached to the block when this asset is applied to the scene. If omitted, CE.SDK will default to a rect shape `//ly.img.ubq/shape/rect`. ``` "shapeType": ShapeType.rect.rawValue, ``` `kind`: The kind that should be set to the block when this asset is applied to the scene. If omitted, CE.SDK will default to an empty string. ``` "kind": "image", ``` `width`: The original width of the image. `height`: The original height of the image. ``` "width": String(image.width), "height": String(image.height), ``` `context`: Adds contextual information to the asset. Right now, this only includes the source id of the source configuration. ``` context: .init(sourceID: "unsplash"), ``` `credits` (optional): Some image providers require to display credits to the asset's artist. If set, it has to be an object with the artist's `name` and a `url` to the artist page. ``` credits: .init(name: image.user.name!, url: image.user.links?.html), ``` `utm` (optional): Some image providers require to add UTM parameters to all links to the source or the artist. If set, it contains a string to the `source` (added as `utm_source`) and the `medium` (added as `utm_medium`) ``` utm: .init(source: "CE.SDK Demo", medium: "referral") ``` After translating the asset to match the interface from the asset source API, the array of assets for the current page can be returned. Going further with our Unsplash integration we need to handle the case when no query was provided. Unsplash requires us to call a different API endpoint (`/photos`) with slightly different parameters but the basics are the same. We need to check for success, calculate `total` and `nextPage` and translate the assets. ``` import Foundation import IMGLYEngine public final class UnsplashAssetSource: NSObject { private lazy var decoder: JSONDecoder = { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase return decoder }() private let host: String private let path: String public init(host: String, path: String = "/unsplashProxy") { self.host = host self.path = path } private struct Endpoint { let path: String let query: [URLQueryItem] static func search(queryData: AssetQueryData) -> Self { Endpoint( path: "/search/photos", query: [ .init(name: "query", value: queryData.query), .init(name: "page", value: String(queryData.page + 1)), .init(name: "per_page", value: String(queryData.perPage)), .init(name: "content_filter", value: "high"), ] ) } static func list(queryData: AssetQueryData) -> Self { Endpoint( path: "/photos", query: [ .init(name: "order_by", value: "popular"), .init(name: "page", value: String(queryData.page + 1)), .init(name: "per_page", value: String(queryData.perPage)), .init(name: "content_filter", value: "high"), ] ) } func url(with host: String, path: String) -> URL? { var components = URLComponents() components.scheme = "https" components.host = host components.path = path + self.path components.queryItems = query return components.url } } } extension UnsplashAssetSource: AssetSource { public static let id = "ly.img.asset.source.unsplash" public var id: String { Self.id } public func findAssets(queryData: AssetQueryData) async throws -> AssetQueryResult { let endpoint: Endpoint = queryData.query? .isEmpty ?? true ? .list(queryData: queryData) : .search(queryData: queryData) let data = try await URLSession.shared.get(endpoint.url(with: host, path: path)!).0 if queryData.query?.isEmpty ?? true { let response = try decoder.decode(UnsplashListResponse.self, from: data) let nextPage = queryData.page + 1 return .init( assets: response.map(AssetResult.init), currentPage: queryData.page, nextPage: nextPage, total: -1 ) } else { let response = try decoder.decode(UnsplashSearchResponse.self, from: data) let (results, total, totalPages) = (response.results, response.total ?? 0, response.totalPages ?? 0) let nextPage = (queryData.page + 1) == totalPages ? -1 : queryData.page + 1 return .init( assets: results.map(AssetResult.init), currentPage: queryData.page, nextPage: nextPage, total: total ) } } public var supportedMIMETypes: [String]? { [MIMEType.jpeg.rawValue] } public var credits: AssetCredits? { .init( name: "Unsplash", url: URL(string: "https://unsplash.com/")! ) } public var license: AssetLicense? { .init( name: "Unsplash license (free)", url: URL(string: "https://unsplash.com/license")! ) } } private extension AssetResult { convenience init(image: UnsplashImage) { self.init( id: image.id, locale: "en", label: image.description ?? image.altDescription, tags: image.tags?.compactMap(\.title), meta: [ "uri": image.urls.full.absoluteString, "thumbUri": image.urls.thumb.absoluteString, "blockType": DesignBlockType.graphic.rawValue, "fillType": FillType.image.rawValue, "shapeType": ShapeType.rect.rawValue, "kind": "image", "width": String(image.width), "height": String(image.height), ], context: .init(sourceID: "unsplash"), credits: .init(name: image.user.name!, url: image.user.links?.html), utm: .init(source: "CE.SDK Demo", medium: "referral") ) } } private extension URLSession { // https://forums.developer.apple.com/forums/thread/727823 // Silences warning: "Non-sendable type '(any URLSessionTaskDelegate)?' exiting main actor-isolated context in call to // non-isolated instance method 'data(from:delegate:)' cannot cross actor boundary" nonisolated func get(_ url: URL) async throws -> (Data, URLResponse) { try await data(from: url) } } ``` We have already seen that an asset can define credits for the artist. Depending on the image provider you might need to add credits and the license for the source. In case of Unsplash, this includes a link as well as the license of all assets from this source. ``` public var credits: AssetCredits? { .init( name: "Unsplash", url: URL(string: "https://unsplash.com/")! ) } public var license: AssetLicense? { .init( name: "Unsplash license (free)", url: URL(string: "https://unsplash.com/license")! ) } ``` ## Local Asset Sources In many cases, you already have various finite sets of assets that you want to make available via asset sources. In order to save you the effort of having to implement custom asset query callbacks for each of these asset sources, CE.SDK also allows you to create "local" asset sources, which are managed by the engine and provide search and pagination functionalities. In order to add such a local asset source, simply call the `addLocalSource` API and choose a unique id with which you can later access the asset source. ``` try engine.asset.addLocalSource(sourceID: "background-videos") ``` The `addAsset(to: String, asset: AssetDefinition)` API allows you to add new asset instances to your local asset source. The local asset source then keeps track of these assets and returns matching items as the result of asset queries. Asset queries return the assets in the same order in which they were inserted into the local asset source. Note that the `AssetDefinition` type that we pass to the `addAsset` API is slightly different than the `AssetResult` type which is returned by asset queries. The `AssetDefinition` for example contains all localizations of the labels and tags of the same asset whereas the `AssetResult` is specific to the locale property of the query. ``` let asset = AssetDefinition(id: "ocean-waves-1", meta: [ "uri": "https://example.com/ocean-waves-1.mp4", "thumbUri": "https://example.com/thumbnails/ocean-waves-1.jpg", "mimeType": "video/mp4", "width": "1920", "height": "1080", ], label: [ "en": "relaxing ocean waves", "es": "olas del mar relajantes", ], tags: [ "en": ["ocean", "waves", "soothing", "slow"], "es": ["mar", "olas", "calmante", "lento"], ]) try engine.asset.addAsset(to: "background-videos", asset: asset) ``` Creating a Scene From an Initial Video URL - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/create-scene-from-video-url/?platform=ios&language=swift # Creating a Scene From an Initial Video URL In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk) with an initial video. Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-create-scene-from-video-url/CreateSceneFromVideoURL.swift) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-create-scene-from-video-url/CreateSceneFromVideoURL.swift) Starting from an existing video allows you to use the editor for customizing individual assets. This is done by using `func create(fromVideo url: URL) async throws -> DesignBlockID` and passing a URL as argument. Specify the source to use for the initial video. This can be a relative path or a remote URL. ``` let scene = try await engine.scene.create(fromVideo: URL(string: "https://img.ly/static/ubq_video_samples/bbb.mp4")!) ``` We can retrieve the graphic block id of this initial video using `func find(byType type: DesignBlockType) throws -> [DesignBlockID]`. Note that that function returns an array. Since there's only a single graphic block in the scene, the block is at index `0`. ``` // Find the automatically added graphic block in the scene that contains the video fill. let block = try engine.block.find(byType: .graphic).first! ``` We can then manipulate and modify this block. Here we modify its opacity with `func setOpacity(_ id: DesignBlockID, value: Float) throws`. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` // Change its opacity. try engine.block.setOpacity(block, value: 0.5) ``` When starting with an initial video, the scene's page dimensions match the given resource and the scene is configured to be in pixel design units. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). Observe Events - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/events?language=swift&platform=ios#setup # Observe Events In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to subscribe to creation, update, and destruction events of design blocks. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ``` let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) ``` ## Subscribing to Events The event API provides a single function to subscribe to design block events. The types of events are: * `'Created'`: The design block was just created. * `'Updated'`: A property of the design block was updated. * `'Destroyed'`: The design block was destroyed. Note that a destroyed block will have become invalid and trying to use Block API functions on it will result in an exception. You can always use the Block API's `isValid` function to verify whether a block is valid before use. All events that occur during an engine update are batched, deduplicated, and always delivered at the very end of the engine update. Deduplication means you will receive at most one `'Updated'` event per block per subscription, even though there could potentially be multiple updates to a block during the engine update. To be clear, this also means the order of the event list provided to your event callback won't reflect the actual order of events within an engine update. ``` public func subscribe(to blocks: [DesignBlockID]) -> AsyncStream<[BlockEvent]> ``` Subscribe to block life-cycle events. * `blocks:`: A list of blocks to filter events by. If the list is empty, events for every block are sent. * Returns: A stream of events. Events are bundled and sent at the end of each engine update. ``` let task = Task { for await events in engine.event.subscribe(to: [block]) { for event in events { print("Event: \(event.type) \(event.block)") if engine.block.isValid(event.block) { let type = try engine.block.getType(event.block) print("Block type: \(type)") } } } } ``` Migrating to v1.19 - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/introduction/migration_1_19?language=swift&platform=ios#initialization Platform Web iOS Catalyst macOS Android Language Swift Platform: iOS Language: Swift Version [v1.19](/docs/cesdk/faq/changelog/#v1190) 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. ``` try await Engine(license: "", userID: "") ``` Please see the [updated Quickstarts](/docs/cesdk/engine/quickstart/) 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** 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** All the functions above still support the string overload variants, however, their usage will cause lint warnings in favor of type safe overloads. **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. ``` /** 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 */ ``` ``` /** 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 */ ``` [ Previous Migrating to v1.13 ](/docs/cesdk/introduction/migration_1_13/)[ Next Migrating to v1.32 ](/docs/cesdk/introduction/migration_1_32/) Creating a Scene From a Blob - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/create-scene-from-image-blob/?platform=ios&language=swift # Creating a Scene From a Blob In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk) with an initial image provided from a blob. Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-create-scene-from-image-blob/CreateSceneFromImageBlob.swift) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-create-scene-from-image-blob/CreateSceneFromImageBlob.swift) Starting from an existing image allows you to use the editor for customizing individual assets. This is done by using `func create(from imageURL: URL, dpi: Float = 300, pixelScaleFactor: Float = 1) async throws -> DesignBlockID` and passing a URL as argument. The `dpi` argument sets the dots per inch of the scene. The `pixelScaleFactor` sets the display's pixel scale factor. First, get hold of a `blob` by fetching an image from the web. This is just for demonstration purposes and your `blob` object may come from a different source. ``` let blob = try await URLSession.shared.data(from: .init(string: "https://img.ly/static/ubq_samples/sample_4.jpg")!).0 ``` Afterward, create a temporary URL and save the `Data`. ``` let url = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString) .appendingPathExtension("jpg") try blob.write(to: url, options: .atomic) ``` Use the created URL as a source for the initial image. ``` let scene = try await engine.scene.create(fromImage: url) ``` We can retrieve the graphic block id of this initial image using `func find(byType type: DesignBlockType) throws -> [DesignBlockID]`. Note that that function returns an array. Since there's only a single graphic block in the scene, the block is at index `0`. ``` // Find the automatically added graphic block in the scene that contains the image fill. let block = try engine.block.find(byType: .graphic).first! ``` We can then manipulate and modify this block. Here we modify its opacity with `func setOpacity(_ id: DesignBlockID, value: Float) throws`. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` // Change its opacity. try engine.block.setOpacity(block, value: 0.5) ``` When starting with an initial image, the scenes page dimensions match the given image, and the scene is configured to be in pixel design units. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). Save Scenes to an Archive - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/save-scene-to-archive?language=swift&platform=ios#loading-scene-archives # Save Scenes to an Archive In this example, we will show you how to save scenes as an archive with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-save-scene-to-archive/SaveSceneToArchive.swift) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-save-scene-to-archive/SaveSceneToArchive.swift) The CreativeEngine allows you to save scenes in a binary format to share them between editors or store them for later editing. As an archive, the resulting `Blob` includes all pages and any hidden elements and all the asset data. To get hold of such a `Blob`, you need to use `engine.scene.saveToArchive()`. This is an asynchronous method. After waiting for the coroutine to finish, we receive a `Blob` holding the entire scene currently loaded in the editor including its assets' data. ``` let blob = try await engine.scene.saveToArchive() ``` That `Blob` can then be treated as a form file parameter and sent to a remote location. ``` var request = URLRequest(url: .init(string: "https://example.com/upload/")!) request.httpMethod = "POST" let (data, response) = try await URLSession.shared.upload(for: request, from: blob) ``` ## Loading Scene Archives Loading a scene archives requires unzipping the archives contents to a location, that's accessible to the CreativeEngine. One could for example unzip the archive via `unzip archive.zip` and then serve its contents using `$ npx serve`. This spins up a local test server, that serves everything contained in the current directory at `http://localhost:3000` The archive can then be loaded by calling `await engine.scene.loadFromURL('http://localhost:3000/scene.scene')`. See [loading scenes](/docs/cesdk/engine/guides/load-scene-from-url/) for more details. All asset paths in the archive are then resolved relative to the location of the `scene.scene` file. For an image, that would result in `'http://localhost:3000/images/1234.jpeg'`. After loading all URLs are fully resolved with the location of the `scene.scene` file and the scene behaves like any other scene. ## Resolving assets from a different source The engine will use its [URI resolver](/docs/cesdk/engine/guides/resolve-custom-uri/) to resolve all asset paths it encounters. This allows you to redirect requests for the assets contained in archive to a different location. To do so, you can add a custom resolver, that redirects requests for assets to a different location. Assuming you store your archived scenes in a `scenes/` directory, this would be an example of how to do so: Control Audio & Video - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-video/?platform=ios#time-offset-and-duration # Control Audio & Video In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to configure and control audio and video through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Time Offset and Duration The time offset determines when a block becomes active during playback on the page's timeline, and the duration decides how long this block is active. Blocks within tracks are a special case in that they have an implicitly calculated time offset that is determined by their order and the total duration of their preceding blocks in the same track. As with any audio/video-related property, not every block supports these properties. Use `hasTimeOffset` and `hasDuration` to check. ``` public func supportsTimeOffset(_ id: DesignBlockID) throws -> Bool ``` Returns whether the block has a time offset property. * `id:`: The block to query. * Returns: `true`, if the block has a time offset property. ``` try engine.block.supportsTimeOffset(audio) ``` ``` public func setTimeOffset(_ id: DesignBlockID, offset: Double) throws ``` Set the time offset of the given block relative to its parent. The time offset controls when the block is first active in the timeline. * Note: The time offset is not supported by the page block. * `id`: The block whose time offset should be changed. * `offset`: The new time offset in seconds. ``` try engine.block.setTimeOffset(audio, offset: 2) ``` ``` public func getTimeOffset(_ id: DesignBlockID) throws -> Double ``` Get the time offset of the given block relative to its parent. * `id:`: The block whose time offset should be queried. * Returns: The time offset of the block. ``` try engine.block.getTimeOffset(audio) /* Returns 2 */ ``` ``` public func supportsDuration(_ id: DesignBlockID) throws -> Bool ``` Returns whether the block has a duration property. * `id:`: The block to query. * Returns: `true` if the block has a duration property. ``` try engine.block.supportsDuration(page) ``` ``` public func setDuration(_ id: DesignBlockID, duration: Double) throws ``` Set the playback duration of the given block in seconds. The duration defines for how long the block is active in the scene during playback. If a duration is set on the page block, it becomes the duration source block. * Note: The duration is ignored when the scene is not in "Video" mode. * `id`: The block whose duration should be changed. * `duration`: The new duration in seconds. ``` try engine.block.setDuration(page, duration: 10) ``` ``` public func getDuration(_ id: DesignBlockID) throws -> Double ``` Get the playback duration of the given block in seconds. * `id:`: The block whose duration should be returned. * Returns: The block's duration. ``` try engine.block.getDuration(page) /* Returns 10 */ ``` ``` public func supportsPageDurationSource(_ page: DesignBlockID, id: DesignBlockID) throws -> Bool ``` Returns whether the block can be marked as the element that defines the duration of the given page. * `id:`: The block to query. * Returns: `true`, if the block has a time offset property. ``` try engine.block.supportsPageDurationSource(page, id: block) ``` ``` public func setPageDurationSource(_ page: DesignBlockID, id: DesignBlockID) throws ``` Set an block as duration source so that the overall page duration is automatically determined by this. If no defining block is set, the page duration is calculated over all children. Only one block per page can be marked as duration source. Will automatically unmark the previously marked. Note: This is only supported for blocks that have a duration. * `page:`: The page block for which it should be enabled. * `id:`: The block which should be marked as duration source. ``` try engine.block.setPageDurationSource(page, id: block) ``` ``` public func isPageDurationSource(_ id: DesignBlockID) throws -> Bool ``` Returns whether the block is a duration source block. * `id:`: The block whose duration source property should be queried. * Returns: `true`, if the block is a duration source block. ``` try engine.block.isPageDurationSource(block) ``` ``` public func removePageDurationSource(_ id: DesignBlockID) throws ``` Remove the block as duration source block for the page. If a scene or page is given as block, it is deactivated for all blocks in the scene or page. * `id:`: The block whose duration source property should be removed. ``` try engine.block.removePageDurationSource(page) ``` ``` public func setNativePixelBuffer(_ id: DesignBlockID, buffer: CVPixelBuffer) throws ``` Update the pixels of the given pixel stream fill block. * `id`: The pixel stream fill block. * `buffer`: The buffer to copy the pixel data from. ``` let pixelStreamFill = try engine.block.createFill(.pixelStream) ``` ## Trim You can select a specific range of footage from your audio/video resource by providing a trim offset and a trim length. The footage will loop if the trim's length is shorter than the block's duration. This behavior can also be disabled using the `setLooping` function. ``` public func supportsTrim(_ id: DesignBlockID) throws -> Bool ``` Returns whether the block has trim properties. * `id:`: The block to query. * Returns: `true`, if the block has trim properties. ``` try engine.block.supportsTrim(videoFill) ``` ``` public func setTrimOffset(_ id: DesignBlockID, offset: Double) throws ``` Set the trim offset of the given block or fill. Sets the time in seconds within the fill at which playback of the audio or video clip should begin. * Note: This requires the video or audio clip to be loaded. * `id`: The block whose trim should be updated. * `offset`: The new trim offset. ``` try engine.block.setTrimOffset(videoFill, offset: 1) ``` ``` public func getTrimOffset(_ id: DesignBlockID) throws -> Double ``` Get the trim offset of this block. * Note: This requires the video or audio clip to be loaded. * `id:`: The block whose trim offset should be queried. * Returns: The trim offset in seconds. ``` try engine.block.getTrimOffset(videoFill) /* Returns 1 */ ``` ``` public func setTrimLength(_ id: DesignBlockID, length: Double) throws ``` Set the trim length of the given block or fill. The trim length is the duration of the audio or video clip that should be used for playback. * Note: After reaching this value during playback, the trim region will loop. * Note: This requires the video or audio clip to be loaded. * `id`: The object whose trim length should be updated. * `length`: The new trim length in seconds. ``` try engine.block.setTrimLength(videoFill, length: 5) ``` ``` public func getTrimLength(_ id: DesignBlockID) throws -> Double ``` Get the trim length of the given block or fill. * `id:`: The object whose trim length should be queried. * Returns: The trim length of the object. ``` try engine.block.getTrimLength(videoFill) /* Returns 5 */ ``` ## Playback Control You can start and pause playback and seek to a certain point on the scene's timeline. There's also a solo playback mode to preview audio and video blocks individually while the rest of the scene stays frozen. Finally, you can enable or disable the looping behavior of blocks and control their audio volume. ``` public func setPlaying(_ id: DesignBlockID, enabled: Bool) throws ``` Set whether the block should be during active playback. * `id`: The block that should be updated. * `enabled`: Whether the block should be playing its contents. ``` try engine.block.setPlaying(page, enabled: true) ``` ``` public func isPlaying(_ id: DesignBlockID) throws -> Bool ``` Returns whether the block is currently during active playback. * `id:`: The block to query. * Returns: Whether the block is during playback. ``` try engine.block.isPlaying(page) ``` ``` public func setSoloPlaybackEnabled(_ id: DesignBlockID, enabled: Bool) throws ``` Set whether the given block or fill should play its contents while the rest of the scene remains paused. * Note: Setting this to true for one block will automatically set it to false on all other blocks. * `id`: The block or fill to update. * `enabled`: Whether the block's playback should progress as time moves on. ``` try engine.block.setSoloPlaybackEnabled(videoFill, enabled: true) ``` ``` public func isSoloPlaybackEnabled(_ id: DesignBlockID) throws -> Bool ``` Return whether the given block or fill is currently set to play its contents while the rest of the scene remains paused. * `id:`: The block or fill to query. * Returns: Whether solo playback is enabled for this block. ``` try engine.block.isSoloPlaybackEnabled(videoFill) ``` ``` public func supportsPlaybackTime(_ id: DesignBlockID) throws -> Bool ``` Returns whether the block has a playback time property. * `id:`: The block to query. * Returns: Whether the block has a playback time property. ``` try engine.block.supportsPlaybackTime(page) ``` ``` public func setPlaybackTime(_ id: DesignBlockID, time: Double) throws ``` Set the playback time of the given block. * `id`: The block whose playback time should be updated. * `time`: The new playback time of the block in seconds. ``` try engine.block.setPlaybackTime(page, time: 1) ``` ``` public func getPlaybackTime(_ id: DesignBlockID) throws -> Double ``` Get the playback time of the given block. * `id:`: The block to query. * Returns: The playback time of the block in seconds. ``` try engine.block.getPlaybackTime(page) ``` ``` public func isVisibleAtCurrentPlaybackTime(_ id: DesignBlockID) throws -> Bool ``` Returns whether the block is visible on the canvas at the current playback time. * `id:`: The block to query. * Returns: The visibility state. ``` try engine.block.isVisibleAtCurrentPlaybackTime(block) ``` ``` public func supportsPlaybackControl(_ id: DesignBlockID) throws -> Bool ``` Returns whether the block supports a playback control. * `id:`: The block to query. * Returns: Whether the block has playback control. ``` try engine.block.supportsPlaybackControl(videoFill) ``` ``` public func setLooping(_ id: DesignBlockID, looping: Bool) throws ``` Set whether the block should start from the beginning again or stop. * `id`: The block or video fill to update. * `looping`: Whether the block should loop to the beginning or stop. ``` try engine.block.setLooping(videoFill, looping: true) ``` ``` public func isLooping(_ id: DesignBlockID) throws -> Bool ``` Query whether the block is looping. * `id:`: The block to query. * Returns: Whether the block is looping. ``` try engine.block.isLooping(videoFill) ``` ``` public func setMuted(_ id: DesignBlockID, muted: Bool) throws ``` Set whether the audio of the block is muted. * `id`: The block or video fill to update. * `muted`: Whether the audio should be muted. ``` try engine.block.setMuted(videoFill, muted: true) ``` ``` public func isMuted(_ id: DesignBlockID) throws -> Bool ``` Query whether the block is muted. * `id:`: The block to query. * Returns: The volume with a range of `0, 1`. ``` try engine.block.isMuted(videoFill) ``` ``` public func setVolume(_ id: DesignBlockID, volume: Float) throws ``` Set the audio volume of the given block. * `id`: The block or video fill to update. * `volume`: The desired volume with a range of `0, 1`. ``` try engine.block.setVolume(videoFill, volume: 0.5) /* 50% volume */ ``` ``` public func getVolume(_ id: DesignBlockID) throws -> Float ``` Get the audio volume of the given block. * `id:`: The block to query. * Returns: The volume with a range of `0, 1`. ``` try engine.block.getVolume(videoFill) ``` ``` public func getVideoWidth(_ id: DesignBlockID) throws -> Int ``` Get the video width in pixels of the video resource that is attached to the given block. * `block:`: The video fill. * Returns: The video width in pixels. ``` try engine.block.getVideoWidth(videoFill) ``` ``` public func getVideoHeight(_ id: DesignBlockID) throws -> Int ``` Get the video height in pixels of the video resource that is attached to the given block. * `block:`: The video fill. * Returns: The video height in pixels. ``` try engine.block.getVideoHeight(videoFill) ``` ## Resource Control Until an audio/video resource referenced by a block is loaded, properties like the duration of the resource aren't available, and accessing those will lead to an error. You can avoid this by forcing the resource you want to access to load using `forceLoadAVResource`. ``` public func forceLoadAVResource(_ id: DesignBlockID) async throws ``` Begins loading the required audio and video resource for the given video fill or audio block. * `id:`: The video fill or audio block whose resource should be loaded. ``` try await engine.block.forceLoadAVResource(videoFill) ``` ``` public func unstable_isAVResourceLoaded(_ id: DesignBlockID) throws -> Bool ``` Returns whether the audio and video resource for the given video fill or audio block is loaded. * `id:`: The video fill or audio block. * Returns: Whether the resource is loaded. ``` try engine.block.unstable_isAVResourceLoaded(videoFill) ``` ``` public func getAVResourceTotalDuration(_ id: DesignBlockID) throws -> Double ``` Get the duration in seconds of the video or audio resource that is attached to the given block. * `id:`: The video fill or audio block. * Returns: The video or audio file duration. ``` try engine.block.getAVResourceTotalDuration(videoFill) ``` ## Thumbnail Previews For a user interface, it can be helpful to have image previews in the form of thumbnails for any given video resource. For videos, the engine can provide one or more frames using `generateVideoThumbnailSequence`. Pass the video fill that references the video resource. In addition to video thumbnails, the engine can also render compositions of design blocks over time. To do this pass in the respective design block. The video editor uses these to visually represent blocks in the timeline. In order to visualize audio signals `generateAudioThumbnailSequence` can be used. This generates a sequence of values in the range of 0 to 1 that represent the loudness of the signal. These values can be used to render a waveform pattern in any custom style. Note: there can be at most one thumbnail generation request per block at any given time. If you don't want to wait for the request to finish before issuing a new request, you can cancel the task. ``` public func generateVideoThumbnailSequence(_ id: DesignBlockID, thumbnailHeight: Int, timeRange: ClosedRange, numberOfFrames: Int) -> AsyncThrowingStream ``` Generate a thumbnail sequence for the given video fill or design block. * Note: There can only be one thumbnail generation request in progress for a given block. * `id`: A video fill or a design block. * `thumbnailHeight`: The height of a thumbnail. The width will be calculated from the video aspect ratio. * `timeRange`: The time range of the generated thumbnails relative to the time offset of the design block. * `numberOfFrames`: The number of thumbnails to generate within the given time range. * Returns: A stream of VideoThumbnail objects. ``` let videoThumbnailTask = Task { for try await thumbnail in engine.block.generateVideoThumbnailSequence( videoFill, /* video fill or page */ thumbnailHeight: 128, /* width will be calculated from aspect ratio */ timeRange: 0.5 ... 9.5, /* inclusive time range in seconds */ numberOfFrames: 10 /* number of thumbnails to generate */ ) { if Task.isCancelled { break } // Use the thumbnail... } } ``` ``` public func generateAudioThumbnailSequence(_ id: DesignBlockID, samplesPerChunk: Int, timeRange: ClosedRange, numberOfSamples: Int, numberOfChannels: Int) -> AsyncThrowingStream ``` Generate a thumbnail sequence for the given audio block or video fill. A thumbnail in this case is a chunk of samples in the range of 0 to 1. In case stereo data is requested, the samples are interleaved, starting with the left channel. * `id`: The audio block or video fill. * `samplesPerChunk`: The number of samples per chunk. * `timeRange`: The time range of the generated thumbnails. * `numberOfSamples`: The total number of samples to generate. * `numberOfChannels`: The number of channels in the output. 1 for mono, 2 for stereo. * Returns: A stream of AudioThumbnail objects. ``` let audioThumbnailTask = Task { for try await thumbnail in engine.block.generateAudioThumbnailSequence( audio, samplesPerChunk: 20, timeRange: 0.5...9.5, numberOfSamples: 10 * 20, numberOfChannels: 2 ) { if Task.isCancelled { break } // Draw wave pattern... } } ``` Using the Design Editor - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/mobile-editor/solutions/design-editor/?platform=ios&language=swift # Using the Design Editor `Design Editor` is built to support versatile editing capabilities for a broad range of design applications. Toggling from edit and page overview modes enables users to quickly evaluate and change multi-page designs. A dock at the bottom of the editor provides quick access to most essential editing options in order of relevance allowing users to overlay text, add images, shapes, stickers and upload new image assets. In this example, we will show you how to initialize the `Design Editor` solution for the mobile editor on iOS. The mobile editor is implemented entirely with SwiftUI and this example assumes that you also use SwiftUI to integrate it, however, you can check the UIKit implementation sample on the [quickstart](/docs/cesdk/mobile-editor/quickstart?framework=uikit) page. It can be also applied to this example. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-solutions-design-editor/DesignEditorSolution.swift). * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-solutions-design-editor/DesignEditorSolution.swift) ## Import After [adding the IMGLYUI Swift Package](/docs/cesdk/mobile-editor/quickstart/#using-swift-package-manager) to your app. You can get started right away by importing the editor module into your own code. ``` import IMGLYDesignEditor ``` ## Initialization The editor is initialized with `EngineSettings` which are used to initialize the underlying [Engine](/docs/cesdk/engine/quickstart/). The license key that you received from IMG.LY is the only required parameter. Additionally, you should provide an optional unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU) and it is especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. For more details on how to configure the editor, visit the [configuration](/docs/cesdk/mobile-editor/configuration/) page. ``` let settings = EngineSettings(license: secrets.licenseKey, userID: "") var editor: some View { DesignEditor(settings) } ``` ## Presentation In this integration example the editor is presented as a modal view after tapping a button. Check out the [quickstart](/docs/cesdk/mobile-editor/quickstart/#environment) page for details on the expected environment for the editor and the `ModalEditor` helper. ``` @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } ``` Exporting Blocks - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-export?language=swift&platform=ios#setup # Exporting Blocks In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine export pages or blocks with the `block` API. Exporting allows fine-grained control of the target format. CE.SDK currently supports exporting as PNG, JPEG, TGA, BINARY, PDF and MP4. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Export a Static Design ``` func export(_ id: DesignBlockID, mimeType: MIMEType, options: ExportOptions = .init(), onPreExport: @Sendable (_ engine: Worker) async throws -> Void = { _ in }) async throws -> Blob ``` Exports a design block element as a file of the given mime type. Performs an internal update to resolve the final layout for the blocks. * `id`: The design block element to export. * `mimeType`: The mime type of the output file. * `options`: The options for exporting the block type. * `onPreExport`: The closure to configure the engine before export. Note that the `engine` parameter of this closure is a separate engine that runs in the background. * Returns: The exported data. ``` let exportOptions = ExportOptions( /** * The PNG compression level to use, when exporting to PNG. * Valid values are 0 to 9, higher means smaller, but slower. * Quality is not affected. * Ignored for other encodings. * The default value is 5. */ pngCompressionLevel: 5, /** * The JPEG quality to use when encoding to JPEG. * Valid values are (0F-1F], higher means better quality. * Ignored for other encodings. * The default value is 0.9F. */ jpegQuality: 0.9, /** * The WebP quality to use when encoding to WebP. Valid values are (0-1], higher means better quality. * WebP uses a special lossless encoding that usually produces smaller file sizes than PNG. * Ignored for other encodings. Defaults to 1.0. */ webpQuality: 1.0, /** * An optional target width used in conjunction with target height. * If used, the block will be rendered large enough, that it fills the target * size entirely while maintaining its aspect ratio. * The default value is 0. */ targetWidth: 0, /** * An optional target height used in conjunction with target with. * If used, the block will be rendered large enough, that it fills the target * size entirely while maintaining its aspect ratio. * The default value is 0. */ targetHeight: 0, /** * Export the PDF document with a higher compatibility to different PDF viewers. * Bitmap images and some effects like gradients will be rasterized with the DPI * setting instead of embedding them directly. * The default value is true. */ exportPdfWithHighCompatibility: true, /** * Export the PDF document with an underlayer. * An underlayer is generated by adding a graphics block behind the existing elements of the shape of the elements on * the page. */ exportPdfWithUnderlayer: false, /** * The name of the spot color to be used for the underlayer's fill. */ underlayerSpotColorName: "", /** * The adjustment in size of the shape of the underlayer. */ underlayerOffset: 0.0 ) let blob = try await engine.block.export( scene, mimeType: MIMEType.png, options: exportOptions ) ``` ## Export with a Color Mask ``` func exportWithColorMask(_ id: DesignBlockID, mimeType: MIMEType, maskColorR: Float, maskColorG: Float, maskColorB: Float, options: ExportOptions = .init(), onPreExport: @Sendable (_ engine: Worker) async throws -> Void = { _ in }) async throws -> [Blob] ``` Exports a design block element as a file of the given mime type. Performs an internal update to resolve the final layout for the blocks. * `id`: The design block element to export. * `maskColorB`: The red mask color component in the range of 0 to 1. * `maskColorG`: The green mask color component in the range of 0 to 1. * `maskColorB`: The blue mask color component in the range of 0 to 1. * `mimeType`: The mime type of the output file. * `options`: The options for exporting the block type. * `onPreExport`: The closure to configure the engine before export. Note that the `engine` parameter of this closure is a separate engine that runs in the background. * Returns: A list of the exported image data and mask data. ``` let colorMaskedBlob = try await engine.block.exportWithColorMask( ``` ## Export a Video Export a page as a video file of the given mime type. ``` func exportVideo(_ id: DesignBlockID, mimeType: MIMEType = .mp4, options: VideoExportOptions = .init(), onPreExport: @Sendable (_ engine: Worker) async throws -> Void = { _ in }) async throws -> AsyncThrowingStream ``` Exports a design block as a video file of the given mime type. * `id`: The design block element to export. Currently, only page blocks are supported. * `mimeType`: The mime type of the output video file. * `options`: The options for exporting the video. * `onPreExport`: The closure to configure the engine before export. Note that the `engine` parameter of this closure is a separate engine that runs in the background. * Returns: A stream of video export events that can be used to monitor the progress of the export and to receive the exported video data. ``` let videoExportOptions = VideoExportOptions( /** * Determines the encoder feature set and in turn the quality, size and speed of the encoding process. * The default value is `.main`. */ h264Profile: .main, /** * Controls the H.264 encoding level. This relates to parameters used by the encoder such as bit rate, * timings and motion vectors. Defined by the spec are levels 1.0 up to 6.2. To arrive at an integer value, * the level is multiplied by ten. E.g. to get level 5.2, pass a value of 52. * The default value is 52. */ h264Level: 52, /** * The video bitrate in bits per second. The maximum bitrate is determined by h264Profile and h264Level. * If the value is 0, the bitrate is automatically determined by the engine. */ videoBitrate: 0, /** * The audio bitrate in bits per second. If the value is 0, the bitrate is automatically determined by the engine (128kbps for stereo AAC stream). */ audioBitrate: 0, /** * The target frame rate of the exported video in Hz. * The default value is 30. */ framerate: 30.0, /** * An optional target width used in conjunction with target height. * If used, the block will be rendered large enough, that it fills the target * size entirely while maintaining its aspect ratio. */ targetWidth: 1280, /** * An optional target height used in conjunction with target width. * If used, the block will be rendered large enough, that it fills the target * size entirely while maintaining its aspect ratio. */ targetHeight: 720 ) let exportTask = Task { for try await export in try await engine.block.exportVideo(page, mimeType: MIMEType.mp4, options: videoExportOptions) { switch export { case let .progress(renderedFrames, encodedFrames, totalFrames): print("Rendered", renderedFrames, "frames and encoded", encodedFrames, "frames out of", totalFrames) case let .finished(video: videoData): return videoData } } return Blob() } let videoBlob = try await exportTask.value ``` ## Export Information Before exporting, the maximum export size and available memory can be queried. ``` public func getMaxExportSize() throws -> Int ``` Get the export size limit in pixels on the current device. An export is only possible when both the width and height of the output are below or equal this limit. However, this is only an upper limit as the export might also not be possible due to other reasons, e.g., memory constraints. * Returns: The upper export size limit in pixels or an unlimited size, i.e, the maximum signed 32-bit integer value, if the limit is unknown. ``` let maxExportSizeInPixels = engine.editor.getMaxExportSize() ``` ``` public func getAvailableMemory() throws -> Int64 ``` Get the currently available memory in bytes * Returns: The available memory in bytes. ``` let availableMemoryInBytes = engine.editor.getAvailableMemory() ``` State - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-state?language=swift&platform=ios#setup # State In this example, we will show you how to use [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to retrieve's a block's state and to manually set a block in a pending state, an error state or back to a ready state. Blocks can perform operations that take some time or that can end in bad results. When that happens, blocks put themselves in a pending state or an error state and visual feedback is shown pertaining to the state. When an external operation is done to blocks, for example with a plugin, you may want to manually set the block's state to pending (if that external operation takes time) or to error (if that operation resulted in an error). The possible states of a block are: When calling `getState`, the returned state reflects the combined state of a block, the block's fill, the block's shape and the block's effects. If any of these blocks is in an `.error` state, the returned state will reflect that error. If none of these blocks is in error state but any is in `.pending` state, the returned state will reflect the aggregate progress of the block's progress. If none of the blocks are in error state or pending state, the returned state is `.ready`. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ``` public func getState(_ id: DesignBlockID) throws -> BlockState ``` Get the current state of a block. * Note: If this block is in error state or this block has a `Shape` block, `Fill` block or `Effect` block(s), that is in error state, the returned state will be `.error`. Else, if this block is in pending state or this block has a `Shape` block, `Fill` block or `Effect` block(s), that is in pending state, the returned state will be `.pending`. Else, the returned state will be `.ready`. * `id:`: The block whose state should be queried. * Returns: The state of the block. ``` let state = try engine.block.getState(block) ``` ``` public func setState(_ id: DesignBlockID, state: BlockState) throws ``` Set the state of a block. * `id`: The block whose state should be set. * `state`: The new state to set. ``` try engine.block.setState(block, state: .pending(progress: 0.5)) ``` ``` public func onStateChanged(_ ids: [DesignBlockID]) -> AsyncStream<[DesignBlockID]> ``` Subscribe to changes to the state of a block. * Note: Like `getState`, the state of a block is determined by the state of itself and its `Shape`, `Fill` and `Effect` block(s). * `ids:`: A list of blocks to filter events by. If the list is empty, events for every block are sent. * Returns: A stream of block state change events. ``` let stateTask = Task { ``` Placeholder - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-placeholder?language=swift&platform=ios#setup # Placeholder In this example, we will demonstrate how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to manage placeholder behavior and controls through the block API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Placeholder Behavior and Controls ``` public func supportsPlaceholderBehavior(_ id: DesignBlockID) throws -> Bool ``` Query if the given block supports placeholder behavior. * `id:`: The block to query. * Returns: `true`, if the block supports placeholder behavior. ``` if try engine.block.supportsPlaceholderBehavior(block) { ``` ``` public func setPlaceholderBehaviorEnabled(_ id: DesignBlockID, enabled: Bool) throws ``` Enable or disable the placeholder behavior for a block. * `id`: The block whose placeholder behavior should be enabled or disabled. * `enabled`: Whether the behavior should be enabled or disabled. ``` try engine.block.setPlaceholderBehaviorEnabled(block, enabled: true) ``` ``` public func isPlaceholderBehaviorEnabled(_ id: DesignBlockID) throws -> Bool ``` Query if the given block has placeholder behavior enabled. * `id:`: The block to query. * Returns: `true`, if the block has placeholder behavior enabled. ``` let placeholderBehaviorIsEnabled = try engine.block.isPlaceholderBehaviorEnabled(block) ``` ``` public func setPlaceholderEnabled(_ id: DesignBlockID, enabled: Bool) throws ``` Enable or disable the placeholder function for a block. * `id`: The block whose placeholder function should be enabled or disabled. * `enabled`: Whether the function should be enabled or disabled. ``` try engine.block.setPlaceholderEnabled(block, enabled: true) ``` ``` public func isPlaceholderEnabled(_ id: DesignBlockID) throws -> Bool ``` Query whether the placeholder function for a block is enabled. * `id:`: The block whose placeholder function state should be queried. * Returns: The enabled state of the placeholder function. ``` let placeholderIsEnabled = try engine.block.isPlaceholderEnabled(block) ``` ``` public func supportsPlaceholderControls(_ id: DesignBlockID) throws -> Bool ``` Checks whether the block supports placeholder controls. * `id:`: The block to query. * Returns: `true`, if the block supports placeholder controls. ``` if try engine.block.supportsPlaceholderControls(block) { ``` ``` public func setPlaceholderControlsOverlayEnabled(_ id: DesignBlockID, enabled: Bool) throws ``` Enable or disable the visibility of the placeholder overlay pattern for a block. * `id`: The block whose placeholder overlay should be enabled or disabled. * `enabled`: Whether the placeholder overlay should be shown or not. ``` try engine.block.setPlaceholderControlsOverlayEnabled(block, enabled: true) ``` ``` public func isPlaceholderControlsOverlayEnabled(_ id: DesignBlockID) throws -> Bool ``` Query whether the placeholder overlay pattern for a block is shown. * `id:`: The block whose placeholder overlay visibility state should be queried. * Returns: the visibility state of the block's placeholder overlay pattern. ``` let overlayEnabled = try engine.block.isPlaceholderControlsOverlayEnabled(block) ``` ``` public func setPlaceholderControlsButtonEnabled(_ id: DesignBlockID, enabled: Bool) throws ``` Enable or disable the visibility of the placeholder button for a block. * `id`: The block whose placeholder button should be enabled or disabled. * `enabled`: Whether the placeholder button should be shown or not. ``` try engine.block.setPlaceholderControlsButtonEnabled(block, enabled: true) ``` ``` public func isPlaceholderControlsButtonEnabled(_ id: DesignBlockID) throws -> Bool ``` Query whether the placeholder button for a block is shown. * `id:`: The block whose placeholder button visibility state should be queried. * Returns: the visibility state of the block's placeholder button. ``` let buttonEnabled = try engine.block.isPlaceholderControlsButtonEnabled(block) ``` Integrate the Mobile Camera - CE.SDK | IMG.LY Docs [ios/uikit/swift] https://img.ly/docs/cesdk/mobile-camera/quickstart/?platform=ios&language=swift&framework=uikit # Integrate the Mobile Camera In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s mobile camera in your iOS app. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/camera-guides-quickstart/). * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/camera-guides-quickstart/) ### Requirements The mobile camera requires iOS 16 and Swift 5.10 (Xcode 15.4) or later. ### Using Swift Package Manager If you use [Swift Package Manager](https://github.com/apple/swift-package-manager) to build your app and want to integrate the Creative Engine and UI modules using your regular workflows, add the [IMGLYUI Swift Package](https://github.com/imgly/IMGLYUI-swift) as a dependency to your project. ![](/docs/cesdk/3c5227dab88fd8e5a0ca5fb8deeb16e1/spm-ui.png) This package provides multiple library products. Add the default `IMGLYUI` library to your app target to add all available UI modules included in this package to your app. To keep your app size minimal, only add the library product that you need, e.g., only add the `IMGLYCamera` library if you need to `import IMGLYCamera` in your code. On the _General_ page of your app target's Xcode project settings the _Frameworks, Libraries, and Embedded Content_ section lists all used library products. ## Usage This example shows the basic usage of the camera. ### Import You can get started right away by importing the camera module into your own code. ``` import IMGLYCamera ``` In this integration example, the camera is presented as a modal view after tapping a button. ``` private lazy var button = UIButton( type: .system, primaryAction: UIAction(title: "Use the Camera") { [unowned self] _ in camera.modalPresentationStyle = .fullScreen present(camera, animated: true) } ) ``` ### Initialization The camera is initialized with `EngineSettings`. You need to provide the license key that you received from IMG.LY. Optionally, you can provide a unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU) and it is especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. ``` Camera(.init(license: secrets.licenseKey, userID: "")) { result in ``` ### Result The `Camera`’s `onDismiss` closure returns a `Result`. If the user has recorded videos, the `.success(_)` case contains the `CameraResult`. ``` switch result { case let .success(.recording(recordings)): let urls = recordings.flatMap { $0.videos.map(\.url) } let recordedVideos = urls // Do something with the recorded videos print(recordedVideos) case .success(.reaction): print("Reaction case not handled here") case let .failure(error): print(error.localizedDescription) self.presentedViewController?.dismiss(animated: true) } ``` Since the Mobile Camera is entirely written in SwiftUI, it needs to be integrated with a `UIHostingController` object into a UIKit view hierarchy. ``` private lazy var camera = UIHostingController(rootView: Camera(.init(license: secrets.licenseKey, userID: "")) { result in switch result { case let .success(.recording(recordings)): let urls = recordings.flatMap { $0.videos.map(\.url) } let recordedVideos = urls // Do something with the recorded videos print(recordedVideos) case .success(.reaction): print("Reaction case not handled here") case let .failure(error): print(error.localizedDescription) self.presentedViewController?.dismiss(animated: true) } }) ``` That is all. For more than basic configuration, check out all the available [configurations](/docs/cesdk/mobile-camera/configuration/). Zoom - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/scene-zoom?language=swift&platform=ios#setup # Zoom In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to control and observe camera zoom via the `scene` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` public func getZoom() throws -> Float ``` Query a camera zoom level of the active scene. * Returns: Returns the current zoom level of the scene in unit 1/px, i.e., how large a pixel of the camera resolution is shown on the screen. A zoom level of 2.0f results in one pixel in the design to be two pixels on the screen. ``` try engine.scene.setZoom(0.5 * engine.scene.getZoom()) ``` ``` public func setZoom(_ level: Float) throws ``` Sets the zoom level of the active scene. A zoom level of 2.0f results in one pixel in the design to be two pixels on the screen. * `level:`: The new zoom level with unit 1/px, i.e., how large a pixel of the camera resolution is shown on the screen. ``` try engine.scene.setZoom(1.0) ``` ``` public func zoom(to id: DesignBlockID, paddingLeft: Float = 0, paddingTop: Float = 0, paddingRight: Float = 0, paddingBottom: Float = 0) async throws ``` Sets the zoom and focus to show a block. Without padding, this results in a tight view on the block. * `id`: The block that should be focussed on. * `paddingLeft`: Optional padding in points to the left of the block. * `paddingTop`: Optional padding in points to the top of the block. * `paddingRight`: Optional padding in points to the right of the block. * `paddingBottom`: Optional padding in points to the bottom of the block. ``` try await engine.scene.zoom(to: scene, paddingLeft: 20.0, paddingTop: 20.0, paddingRight: 20.0, paddingBottom: 20.0) ``` ``` public func immediateZoom(to id: DesignBlockID, paddingLeft: Float = 0, paddingTop: Float = 0, paddingRight: Float = 0, paddingBottom: Float = 0, forceUpdate: Bool = false) throws ``` Sets the zoom and focus to show a block. Without padding, this results in a tight view on the block. Assums layout has been done. You can force the layout with explicit update call that will update the layout. * `id`: The block that should be focussed on. * `paddingLeft`: Optional padding in points to the left of the block. * `paddingTop`: Optional padding in points to the top of the block. * `paddingRight`: Optional padding in points to the right of the block. * `paddingBottom`: Optional padding in points to the bottom of the block. * `forceUpdate`: Optional flag that will run the system update that will update the layout. ``` try engine.scene.immediateZoom(to: scene, paddingLeft: 20.0, paddingTop: 20.0, paddingRight: 20.0, paddingBottom: 20.0) ``` ``` public func enableZoomAutoFit(_ id: DesignBlockID, axis: ZoomAutoFitAxis, paddingLeft: Float = 0, paddingTop: Float = 0, paddingRight: Float = 0, paddingBottom: Float = 0) throws ``` Continually adjusts the zoom level to fit the width or height of a block's axis-aligned bounding box. This only shows an effect if the zoom level is not handled/overwritten by the UI. Without padding, this results in a tight view on the block. * Note: Calling `setZoom(level:)` or `zoom(to:)` disables the continuous adjustment. * `id`: The block for which the zoom is adjusted. * `axis`: The block axis for which the zoom is adjusted. * `paddingLeft`: Optional padding in points to the left of the block. * `paddingTop`: Optional padding in points to the top of the block. * `paddingRight`: Optional padding in points to the right of the block. * `paddingBottom`: Optional padding in points to the bottom of the block. ``` try engine.scene.enableZoomAutoFit( page, axis: .both, paddingLeft: 20, paddingTop: 20, paddingRight: 20, paddingBottom: 20 ) ``` ``` public func disableZoomAutoFit(_ id: DesignBlockID) throws ``` Disables any previously set zoom auto-fit. * `id:`: The scene or a block in the scene for which to disable zoom auto-fit. ``` try engine.scene.disableZoomAutoFit(page) ``` ``` public func isZoomAutoFitEnabled(_ id: DesignBlockID) throws -> Bool ``` Queries whether zoom auto-fit is enabled. * `id:`: The scene or a block in the scene for which to query the zoom auto-fit. * Returns: `true` if the given block has auto-fit set or the scene contains a block for which auto-fit is set, `false` otherwise. ``` try engine.scene.isZoomAutoFitEnabled(page) ``` ``` public func unstable_enableCameraPositionClamping(_ ids: [DesignBlockID], paddingLeft: Float = 0, paddingTop: Float = 0, paddingRight: Float = 0, paddingBottom: Float = 0, scaledPaddingLeft: Float = 0, scaledPaddingTop: Float = 0, scaledPaddingRight: Float = 0, scaledPaddingBottom: Float = 0) throws ``` Continually ensures the camera position to be within the width and height of the blocks axis-aligned bounding box. Without padding, this results in a tight clamp on the blocks. * `ids`: The blocks for which the camera position is adjusted to, usually, the scene or a page. * `paddingLeft`: Optional padding in points to the left of the block. * `paddingTop`: Optional padding in points to the top of the block. * `paddingRight`: Optional padding in points to the right of the block. * `paddingBottom`: Optional padding in points to the bottom of the block. * `scaledPaddingLeft`: Optional padding in points to the left of the block that scales with the zoom level until five times the initial value. * `scaledPaddingTop`: Optional padding in points to the left of the block that scales with the zoom level until five times the initial value. * `scaledPaddingRight`: Optional padding in points to the left of the block that scales with the zoom level until five times the initial value. * `scaledPaddingBottom`: Optional padding in points to the left of the block that scales with the zoom level until five times the initial value. ``` try engine.scene.unstable_enableCameraPositionClamping( [scene], paddingLeft: 10, paddingTop: 10, paddingRight: 10, paddingBottom: 10, scaledPaddingLeft: 0, scaledPaddingTop: 0, scaledPaddingRight: 0, scaledPaddingBottom: 0 ) ``` ``` public func unstable_disableCameraPositionClamping() throws ``` Disables any previously set position clamping. ``` try engine.scene.unstable_disableCameraPositionClamping() ``` ``` public func unstable_isCameraPositionClampingEnabled(_ id: DesignBlockID) throws -> Bool ``` Queries whether position clamping is enabled. * `id:`: The scene or a block in the scene for which to query the position clamping. * Returns: `true` if the given block has position clamping set or the scene contains a block for which position clamping is set, `false` otherwise. ``` try engine.scene.unstable_isCameraPositionClampingEnabled(scene) ``` ``` public func unstable_enableCameraZoomClamping(_ ids: [DesignBlockID], minZoomLimit: Float = -1, maxZoomLimit: Float = -1, paddingLeft: Float = 0, paddingTop: Float = 0, paddingRight: Float = 0, paddingBottom: Float = 0) throws ``` Continually ensures the zoom level of the camera in the active scene to be in the given range. * `ids`: The blocks for which the camera zoom limits are adjusted to, usually, the scene or a page. * `minZoomLimit`: The minimum zoom limit in unit `dpx/dot` when zooming out, unlimited when negative. * `maxZoomLimit`: The maximum zoom limit in unit `dpx/dot` when zooming in, unlimited when negative. * `paddingLeft`: Optional padding in points to the left of the block. Only applied when the block is not a camera. * `paddingTop`: Optional padding in points to the top of the block. Only applied when the block is not a camera. * `paddingRight`: Optional padding in points to the right of the block. Only applied when the block is not a camera. * `paddingBottom`: Optional padding in points to the bottom of the block. Only applied when the block is not a camera. ``` public func unstable_disableCameraZoomClamping() throws ``` Disables previously set zoom clamping for the block, scene, or camera. ``` try engine.scene.unstable_disableCameraZoomClamping() ``` ``` public func unstable_isCameraZoomClampingEnabled(_ id: DesignBlockID) throws -> Bool ``` Queries whether zoom clamping is enabled. * `id:`: The scene or a block for which to query the zoom clamping. * Returns: `true` if the active scene has zoom clamping set, `false` otherwise. ``` try engine.scene.unstable_isCameraZoomClampingEnabled(scene) ``` ``` public var onZoomLevelChanged: AsyncStream { get } ``` Subscribe to changes to the zoom level. ``` let task = Task { ``` ## Settings See clamp camera settings in the [editor settings](/docs/cesdk/engine/api/editor-settings/#clampcamerasettings). Configure Callbacks - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/mobile-editor/configuration/callbacks/?platform=ios&language=swift # Configure Callbacks 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](/docs/cesdk/mobile-editor/solutions/). Note that the bodies of all callbacks except `onUpload` are copied from the `Design Editor` default implementations. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-configuration-callbacks/CallbacksEditorSolution.swift). * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-configuration-callbacks/CallbacksEditorSolution.swift) ## 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. ``` import IMGLYDesignEditor 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](/docs/cesdk/mobile-editor/solutions/) 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. ``` DesignEditor(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](/docs/cesdk/engine/guides/load-scene/) or [create](/docs/cesdk/engine/guides/create-scene/) 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. ``` .imgly.onCreate { engine in // Load or create scene try await engine.scene.load(from: DesignEditor.defaultScene) // or `engine.scene.create*` // Add asset sources try await engine.addDefaultAssetSources(baseURL: Engine.assetBaseURL) try await engine.addDemoAssetSources(sceneMode: engine.scene.getMode(), withUploadAssetSources: true) try await engine.asset.addSource(TextAssetSource(engine: engine)) } ``` * `onExport` - the callback that is invoked when the export button is tapped. You may want to call one of the [export functions](/docs/cesdk/engine/api/block-export/) 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. ``` .imgly.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(0))) 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 scene based on `SceneMode` let data: Data, mimeType: MIMEType switch try mainEngine.scene.getMode() { case .design: (data, mimeType) = try await export() case .video: (data, mimeType) = try await exportVideo() @unknown default: throw CallbackError.unknownSceneMode } // Write and share file let url = FileManager.default.temporaryDirectory.appendingPathComponent( "Export", conformingTo: mimeType.uniformType ) try data.write(to: url, options: [.atomic]) switch try mainEngine.scene.getMode() { case .design: eventHandler.send(.shareFile(url)) case .video: eventHandler.send(.exportCompleted { eventHandler.send(.shareFile(url)) }) @unknown default: throw CallbackError.unknownSceneMode } } ``` * `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. ``` .imgly.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) } ``` Manage Assets - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/assets?language=swift&platform=ios#setup # Manage Assets In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to manage assets through the `asset` API. To begin working with assets first you need at least one asset source. As the name might imply asset sources provide the engine with assets. These assets then show up in the editor's asset library. But they can also be independently searched and used to create design blocks. Asset sources can be added dynamically using the `asset` API as we will show in this guide. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Defining a Custom Asset Source Asset sources need at least an `id` and a `findAssets` function. You may notice asset source functions are all `async`. This way you can use web requests or other long-running operations inside them and return results asynchronously. ``` let customSource = CustomAssetSource(engine: engine) ``` All functions of the `asset` API refer to an asset source by its unique `id`. That's why it has to be mandatory. Trying to add an asset source with an already registered `id` will fail. ``` var id: String { "foobar" } ``` ## Finding and Applying Assets The `findAssets` function should return paginated asset results for the given `queryData`. The asset results have a set of mandatory and optional properties. For a listing with an explanation for each property please refer to the [Integrate a Custom Asset Source](/docs/cesdk/engine/guides/integrate-a-custom-asset-source/) guide. The properties of the `queryData` and the pagination mechanism are also explained in this guide. ``` public func findAssets(sourceID: String, query: AssetQueryData) async throws -> AssetQueryResult ``` Finds assets of a given type in a specific asset source. * `sourceID`: The ID of the asset source. * `query`: All the options to filter the search results by. * Returns: The search results. ``` let result = try await engine.asset.findAssets( sourceID: customSource.id, query: .init(query: "", page: 0, perPage: 10) ) let asset = result.assets[0] let sortByNewest = try await engine.asset.findAssets( sourceID: customSource.id, query: .init(query: nil, page: 0, perPage: 10, sortingOrder: .descending) ) let sortById = try await engine.asset.findAssets( sourceID: customSource.id, query: .init(query: nil, page: 0, perPage: 10, sortingOrder: .ascending, sortKey: "id") ) let sortByMetaKeyValue = try await engine.asset.findAssets( sourceID: customSource.id, query: .init(query: nil, page: 0, perPage: 10, sortingOrder: .ascending, sortKey: "someMetaKey") ) let search = try await engine.asset.findAssets( sourceID: customSource.id, query: .init(query: "banana", page: 0, perPage: 100) ) ``` The optional function 'applyAsset' is to define the behavior of what to do when an asset gets applied to the scene. You can use the engine's APIs to do whatever you want with the given asset result. In this case, we always create an image block and add it to the first page we find. If you don't provide this function the engine's default behavior is to create a block based on the asset result's `meta.blockType` property, add the block to the active page, and sensibly position and size it. ``` public func apply(sourceID: String, assetResult: AssetResult) async throws -> DesignBlockID? ``` Apply an asset result to the active scene. The default behavior will instantiate a block and configure it according to the asset's properties. * Note: that this can be overridden by providing an `applyAsset` function when adding the asset source. * `sourceID`: The ID of the asset source. * `assetResult`: A single assetResult of a `findAssets` query. ``` try await engine.asset.apply(sourceID: customSource.id, assetResult: asset) ``` ``` public func defaultApplyAsset(assetResult: AssetResult) async throws -> DesignBlockID? ``` The default implementation for applying an asset to the scene. This implementation is used when no `applyAsset` function is provided to `addSource`. * `assetResult:`: A single assetResult of a `findAssets` query. ``` if let id = try await engine?.asset.defaultApplyAsset(assetResult: asset) { ``` ``` public func applyToBlock(sourceID: String, assetResult: AssetResult, block: DesignBlockID) async throws ``` Apply an asset result to the given block. * `sourceID`: The ID of the asset source. * `assetResult`: A single assetResult of a `findAssets` query. * `block`: The block the asset should be applied to. ``` try await engine.asset.applyToBlock(sourceID: customSource.id, assetResult: asset, block: block) ``` ``` public func defaultApplyAssetToBlock(assetResult: AssetResult, block: DesignBlockID) async throws ``` The default implementation for applying an asset to an existing block. This implementation is used when no `applyAssetToBlock` function is provided to `addSource`. * `assetResult`: A single assetResult of a `findAssets` query. * `block`: The block to apply the asset result to. ``` try await engine?.asset.defaultApplyAssetToBlock(assetResult: asset, block: block) ``` ``` public func getSupportedMIMETypes(sourceID: String) throws -> [String] ``` Queries the list of supported mime types of the specified asset source. * `sourceID:`: The ID of the asset source. * Returns: An empty result means that all mime types are supported. ``` let mimeTypes = try engine.asset.getSupportedMIMETypes(sourceID: customSource.id) ``` ## Registering a New Asset Source ``` public func addSource(_ source: AssetSource) throws ``` Adds a custom asset source. Its ID has to be unique. * `source:`: The asset source. ``` try engine.asset.addSource(customSource) ``` ``` public func addLocalSource(sourceID: String, supportedMimeTypes: [String]? = nil, applyAsset: (@Sendable (AssetResult) async throws -> DesignBlockID?)? = nil, applyAssetToBlock: (@Sendable (AssetResult, DesignBlockID) async throws -> Void)? = nil) throws ``` Adds a local asset source. Its ID has to be unique. * `sourceID`: The asset source. * `supportedMimeTypes`: The mime types of assets that are allowed to be added to this local source. * `applyAsset`: An optional callback that can be used to override the default behavior of applying a given asset result to the active scene. Returns the newly created block or `nil` if a new block was not created. * `applyAssetToBlock`: An optional callback that can be used to override the default behavior of applying an asset result to a given block. ``` let localSourceID = "local-source" try engine.asset.addLocalSource(sourceID: localSourceID) ``` ``` public func findAllSources() -> [String] ``` Finds all registered asset sources. * Returns: A list with the IDs of all registered asset sources. ``` engine.asset.findAllSources() ``` ``` public func removeSource(sourceID: String) throws ``` Removes an asset source with the given ID. * `sourceID:`: The ID to refer to the asset source. ``` try engine.asset.removeSource(sourceID: customSource.id) try engine.asset.removeSource(sourceID: localSourceID) ``` ``` public var onAssetSourceAdded: AsyncStream { get } ``` Subscribe to changes whenever an asset source is added. ``` let addedTask = Task { for await sourceID in engine.asset.onAssetSourceAdded { print("Added source: \(sourceID)") } } ``` ``` public var onAssetSourceRemoved: AsyncStream { get } ``` Subscribe to changes whenever an asset source is removed. ``` let removedTask = Task { for await sourceID in engine.asset.onAssetSourceRemoved { print("Removed source: \(sourceID)") } } ``` ``` public var onAssetSourceUpdated: AsyncStream { get } ``` Subscribe to changes whenever asset source's content is updated. ``` let updatedTask = Task { for await sourceID in engine.asset.onAssetSourceUpdated { print("Updated source: \(sourceID)") } } ``` ## Scene Asset Sources A scene colors asset source is automatically available that allows listing all colors in the scene. This asset source is read-only and is updated when `findAssets` is called. ``` let sceneColorsResult = try await engine.asset.findAssets( sourceID: "ly.img.scene.colors", query: .init(query: nil, page: 0, perPage: 99999) ) let colorAsset = sceneColorsResult.assets[0] ``` ## Add an Asset ``` public func addAsset(to sourceID: String, asset: AssetDefinition) throws ``` Adds the given asset to an asset source. * `to`: The asset source ID that the asset should be added to. * `asset`: The asset to be added to the asset source. ``` let assetDefinition = AssetDefinition( id: "ocean-waves-1", meta: [ "uri": "https://example.com/ocean-waves-1.mp4", "thumbUri": "https://example.com/thumbnails/ocean-waves-1.jpg", "mimeType": MIMEType.mp4.rawValue, "width": "1920", "height": "1080", ], label: [ "en": "relaxing ocean waves", ], tags: [ "en": ["ocean", "waves", "soothing", "slow"], ] ) try engine.asset.addAsset(to: localSourceID, asset: assetDefinition) ``` ## Remove an Asset ``` public func removeAsset(from sourceID: String, assetID: String) throws ``` Removes the specified asset from its asset source. * `from`: The id of the asset source that currently contains the asset. * `assetID`: The id of the asset to be removed. ``` try engine.asset.removeAsset(from: localSourceID, assetID: assetDefinition.id) ``` ## Asset Source Content Updates If the contents of your custom asset source change, you can call the `assetSourceUpdated` API to later notify all subscribers of the `onAssetSourceUpdated` API. ``` public func assetSourceContentsChanged(sourceID: String) throws ``` Notifies the engine that the contents of an asset source changed. * `sourceID:`: The ID of the asset source. ``` try engine.asset.assetSourceContentsChanged(sourceID: customSource.id) ``` ## Groups in Assets ``` public func getGroups(sourceID: String) async throws -> [String] ``` Queries the asset source's groups for a certain asset type. * `sourceID:`: The ID of the asset source. * Returns: The asset groups. ``` let groups = try await engine.asset.getGroups(sourceID: customSource.id) ``` ## Credits and License ``` public func getCredits(sourceID: String) -> AssetCredits? ``` Queries the asset source's credits info. * `sourceID:`: The ID of the asset source. * Returns: The asset source's credits info consisting of a name and an optional URL. ``` let credits = engine.asset.getCredits(sourceID: customSource.id) ``` ``` public func getLicense(sourceID: String) -> AssetLicense? ``` Queries the asset source's license info. * `sourceID:`: The ID of the asset source. * Returns: The asset source's license info consisting of a name and an optional URL. ``` let license = engine.asset.getLicense(sourceID: customSource.id) ``` Boolean Operations - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-bool-ops?language=swift&platform=ios#setup # Boolean Operations In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to combine blocks through the `block` API with boolean operations. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Combining 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. #### Only the following blocks can be combined * A graphics block * A text block ``` 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. ``` if try engine.block.isCombinable([star, rect]) { ``` ``` 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. ``` let combined = try engine.block.combine([star, rect], booleanOperation: .union) ``` Get recorded videos from the Mobile Camera - CE.SDK | IMG.LY Docs [ios/swiftui/swift] https://img.ly/docs/cesdk/mobile-camera/recordings/?platform=ios&language=swift&framework=swiftui # Get recorded videos from the Mobile Camera Learn how to get the recorded videos from the `Result` type in the `Camera`’s `onDismiss` closure. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/camera-guides-recordings/RecordingsCameraSolution.swift). * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/camera-guides-recordings/RecordingsCameraSolution.swift) ## Success A `Recording` has a `duration` and contains an array of `Video`s. The array contains either one `Video` (for single camera recordings or a video that was reacted to) or two `Video`s (for dual camera recordings.) Each `Video` has: * A `url` to the video file that is stored in a temporary location. Make sure to copy the file to a permanent location if you want to access it later. * A `rect` that contains the position of each video as it was shown in the camera preview. For dual camera recordings, you can use these `CGRect`s to arrange the videos as they were laid out in the camera. ``` case let .success(.recording(recordings)): for recording in recordings { print(recording.duration) for video in recording.videos { print(video.url) print(video.rect) } } case let .success(.reaction(video, reactionRecordings)): print(video.duration) for recording in reactionRecordings { print(recording.duration) for video in recording.videos { print(video.url) print(video.rect) } } ``` ### Standard and Dual Camera If the user has recorded videos, the `.recording` case will contain an array of `Recording`s, each representing a segment of the recorded video. ``` case let .success(.recording(recordings)): for recording in recordings { print(recording.duration) for video in recording.videos { print(video.url) print(video.rect) } } ``` ### Video Reaction If the user has recorded a reaction, the `.reaction` case will contain the video that was reacted to and an array of `Recording`s, each representing a segment of the recorded video. ``` case let .success(.reaction(video, reactionRecordings)): print(video.duration) for recording in reactionRecordings { print(recording.duration) for video in recording.videos { print(video.url) print(video.rect) } } ``` ## Failure The `.failure` case has an associated value of `CameraError`, which is either `.cancelled` if the user has cancelled the camera without recording anything, or `.permissionsMissing` if the user has not allowed accessing their camera and/or microphone. ``` case let .failure(error): print(error.localizedDescription) isPresented = false ``` Cutout - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-cutout?language=swift&platform=ios#setup # Cutout In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine `block` and `editor` API to create and modify a cutout block. Cutout blocks can be created from an SVG path or by performing boolean operations on existing multiple cutout blocks. One can then change a cutout's offset from its underlying path and its type. By default, a cutout's color is set by a spot color. Cutouts of type `.solid` use the spot color `"CutContour"` and cutouts of type `.dashed` use the spot color `"PerfCutContour"`. One can change these defaults to other spot colors. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` public func createCutoutFromPath(_ path: String) throws -> DesignBlockID ``` Create a Cutout block. * `path:`: An SVG string describing a path. * Returns: The handle of the created Cutout. ``` 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") ``` ``` public func createCutoutFromBlocks(ids: [DesignBlockID], vectorizeDistanceThreshold: Float, simplifyDistanceThreshold: Float, useExistingShapeInformation: Bool) throws -> DesignBlockID ``` Creates a cutout whose path will be the contour of the given blocks. The cutout path for each element is derived from one two ways: * Blocks that have already have a vector path (shapes, SVG-based text or stickers). * Blocks that don't have a vector path are vectorized and then, optionally, simplified to eliminate jaggedness (images). * `blocks`: The blocks whose shape will serve as the basis for the cutout's path. * `vectorizeDistanceThreshold`: The maximum number of pixels by which the cutout's path can deviate from the original contour. * `simplifyDistanceThreshold`: The maximum number of pixels by which the simplified cutout path can deviate from the cutout contour. If 0, no simplification step is performed. * `useExistingShapeInformation`: If true, the existing vector paths of the provided blocks will be used to create the cutout. If false, new shape information for the cutout will be generated. * Returns: The handle of the created Cutout. ``` // Create a cutout from the outlines of existing blocks. let mixed = try engine.block.createCutoutFromBlocks( ids: [myBlock], vectorizeDistanceThreshold: 2.0, simplifyDistanceThreshold: 4.0 ) ``` ``` public func createCutoutFromOperation(containing blocks: [DesignBlockID], cutoutOperation: CutoutOperation) throws -> DesignBlockID ``` Perform a boolean operation on the given Cutout blocks. The cutout offset of the new block is 0. The cutout type of the new block is that of the first block. When performing a `.difference` operation, the first block is the block subtracted from. * `blocks`: The blocks with which to perform the operation. * `cutoutOperation`: The boolean operation to perform. * Returns: The newly created block. ``` let unionCircleSquare = try engine.block.createCutoutFromOperation(containing: [circle, square], cutoutOperation: .union) ``` ``` public func setSpotColorForCutoutType(cutoutType: CutoutType, name: String) throws ``` Set the spot color assign to a cutout type. If no spot color is set, type `.solid` is assigned "CutContour" and type `.dashed` is assigned "PerfCutContour". All cutout blocks of the given type will be immediately assigned that spot color. * `cutoutType`: The cutout type. * `name`: The spot color name to assign. ``` try engine.editor.setSpotColorForCutoutType(cutoutType: .dashed, name: "Yellow") ``` ``` public func getSpotColorForCutoutType(cutoutType: CutoutType) throws -> String ``` Get the name of the spot color assigned to a cutout type. * `cutoutType:`: The cutout type. * Returns: The color spot name. ``` let dashedSpotColor = try engine.editor.getSpotColorForCutoutType(cutoutType: .dashed) // "Yellow" ``` Bundle Size - CE.SDK | IMG.LY Docs [ios/undefined/undefined] https://img.ly/docs/cesdk/faq/bundle-size/?platform=ios Platform iOS Android Platform: iOS # Bundle Size Get help with any questions regarding the engine and showcases size #### What is the download size of the engine? The `IMGLYEngine.xcframework` file, which is downloaded by Swift Package Manager or Cocoapods, has a compressed size exceeding 130 MB. However, it's essential to understand that this does not directly translate to an equivalent increase in your application's size. In fact, the framework itself will only add around **11.9 MB** to your app's download size, which is relatively small considering the rich feature set that IMGLYEngine provides. The actual impact on your app's size may vary depending on various factors, as discussed in the sections below. #### What is the approximate download size of the mobile editor and the mobile camera? The [mobile editor](/docs/cesdk/mobile-editor/) and [mobile camera](/docs/cesdk/mobile-camera/) are part of the [IMGLYUI package](https://github.com/imgly/IMGLYUI-swift) built on top of the [IMGLYEngine](https://github.com/imgly/IMGLYEngine-swift). This means that you can expect the download size of the mobile editor and camera to be around the size of the engine plus a few additional megabytes. The precise size increase may depend on the bundled assets (scene files, images, stickers), however, with the default resources you can expect it to be around **3.5 MB** plus the size of the engine. #### Assets IMGLYEngine does not include any assets, such as scene files, images, or stickers. However, the engine does provide a convenient [API](/docs/cesdk/engine/guides/assets-served-from-your-own-servers/) for loading assets from your app's bundle or serving them from a remote location. The size of your assets will directly impact your app's size. #### Architectures IMGLYEngine is designed to support the iOS platform and provides slices for both `x86_64` and `arm64` architectures. The `x86_64` architecture is specifically utilized for running apps within the iOS Simulator, whereas the `arm64` architecture is intended for executing apps on actual iOS devices. #### Debug symbols (dSYMs) Debug symbols, also known as dSYMs, are substantial in size but essential for comprehending crash logs and debugging your application. They establish a link between your app's binary code and the human-readable source code, enabling you to determine the cause of a crash. The `IMGLYEngine.xcframework` file includes debug symbols, which are primarily used for crash symbolication when uploading to external tools. Importantly, they won't negatively impact your app's size. #### Bitcode (BCSymbolMaps) Bitcode is an intermediate representation of your app's binary, which allows Apple to optimize your app's binary for specific devices at the time of download. When you enable Bitcode for your app, the App Store will use this intermediate representation to recompile and optimize your app for each specific device, potentially resulting in smaller binaries and improved performance. IMGLYEngine is built using Xcode 15.4, which has removed Bitcode support. As a result, the appropriate files for Bitcode (BCSymbolMaps) are not included within the package. [ Previous Browser Support ](/docs/cesdk/faq/browser-support/)[ Next CORS/CSP Web Security ](/docs/cesdk/faq/cors-csp-web-security/) Using the Postcard Editor - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/mobile-editor/solutions/postcard-editor/?platform=ios&language=swift # Using the Postcard Editor `Postcard Editor` is built to facilitate optimal post- & greeting- card design, from changing accent colors and selecting fonts to custom messages and pictures. Toggling from edit to preview mode allows reviewing the design in the context of the entire post/greeting card. A floating action button at the bottom of the editor features only the most essential editing options in order of relevance allowing users to overlay text, add images, shapes, stickers and upload new image assets. In this example, we will show you how to initialize the `Postcard Editor` solution for the mobile editor on iOS. The mobile editor is implemented entirely with SwiftUI and this example assumes that you also use SwiftUI to integrate it, however, you can check the UIKit implementation sample on the [quickstart](/docs/cesdk/mobile-editor/quickstart?framework=uikit) page. It can be also applied to this example. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-solutions-postcard-editor/PostcardEditorSolution.swift). * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-solutions-postcard-editor/PostcardEditorSolution.swift) ## Import After [adding the IMGLYUI Swift Package](/docs/cesdk/mobile-editor/quickstart/#using-swift-package-manager) to your app. You can get started right away by importing the editor module into your own code. ``` import IMGLYPostcardEditor ``` ## Initialization The editor is initialized with `EngineSettings` which are used to initialize the underlying [Engine](/docs/cesdk/engine/quickstart/). The license key that you received from IMG.LY is the only required parameter. Additionally, you should provide an optional unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU) and it is especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. For more details on how to configure the editor, visit the [configuration](/docs/cesdk/mobile-editor/configuration/) page. ``` let settings = EngineSettings(license: secrets.licenseKey, userID: "") var editor: some View { PostcardEditor(settings) } ``` ## Presentation In this integration example the editor is presented as a modal view after tapping a button. Check out the [quickstart](/docs/cesdk/mobile-editor/quickstart/#environment) page for details on the expected environment for the editor and the `ModalEditor` helper. ``` @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } ``` Source Sets - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/source-sets?language=swift&platform=ios#drawing # Source Sets Source sets allow specifying an entire set of sources, each with a different size, that should be used for drawing a block. The appropriate source is then dynamically chosen based on the current drawing size. This allows using the same scene to render a preview on a mobile screen using a small image file and a high-resolution file for print in the backend. This guide will show you how to specify source sets both for existing blocks and when defining assets. Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-source-sets?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Source+Sets&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-source-sets). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-source-sets?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Source+Sets&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-source-sets) ### Drawing When an image needs to be drawn, the current drawing size in screen pixels is calculated and the engine looks up the most appropriate source file to draw at that resolution. 1. If a source set is set, the source with the closest size exceeding the drawing size is used 2. If no source set is set, the full resolution image is downscaled to a maximum edge length of 4096 (configurable via `maxImageSize` setting) and drawn to the target area This also gives more control about up- and downsampling to you, as all intermediate resolutions can be generated using tooling of your choice. **Source sets are also used during export of your designs and will resolve to the best matching asset for the export resolution.** ## Setup the scene We first create a new scene with a new page. ``` let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: page, paddingLeft: 50, paddingTop: 50, paddingRight: 50, paddingBottom: 50) ``` ## Using a Source Set for an existing Block To make use of a source set for an existing image fill, we use the `setSourceSet` API. This defines a set of sources and specifies height and width for each of these sources. The engine then chooses the appropriate source during drawing. You may query an existing source set using `getSourceSet`. You can add sources to an existing source set with `addImageFileURIToSourceSet`. ``` let block = try engine.block.create(DesignBlockType.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) let imageFill = try engine.block.createFill(.image) try engine.block.setSourceSet(imageFill, property: "fill/image/sourceSet", sourceSet: [ .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_512x341.jpg")!, width: 512, height: 341 ), .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_1024x683.jpg")!, width: 1024, height: 683 ), .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_2048x1366.jpg")!, width: 2048, height: 1366 ), ]) try engine.block.setFill(block, fill: imageFill) try engine.block.appendChild(to: page, child: block) ``` ## Using a Source Set in an Asset For assets, source sets can be defined in the `payload.sourceSet` field. This is directly translated to the `sourceSet` property when applying the asset. The resulting block is configured in the same way as the one described above. The code demonstrates how to add an asset that defines a source set to a local source and how `applyAsset` handles a populated `payload.sourceSet`. ``` let assetWithSourceSet = AssetDefinition( id: "my-image", meta: [ "kind": "image", "fillType": "//ly.img.ubq/fill/image", ], payload: .init(sourceSet: [ .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_512x341.jpg")!, width: 512, height: 341 ), .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_1024x683.jpg")!, width: 1024, height: 683 ), .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_2048x1366.jpg")!, width: 2048, height: 1366 ), ]) ) ``` ## Video Source Sets Source sets can also be used for video fills. This is done by setting the `sourceSet` property of the video fill. The engine will then use the source with the closest size exceeding the drawing size. Thumbnails will use the smallest source if `features/matchThumbnailSourceToFill` is disabled, which is the default. For low end devices or scenes with large videos, you can force the preview to always use the smallest source when editing by enabling `features/forceLowQualityVideoPreview`. On export, the highest quality source is used in any case. ``` let videoFill = try engine.block.createFill(.video) try engine.block.setSourceSet(videoFill, property: "fill/video/sourceSet", sourceSet: [ .init( uri: URL(string: "https://img.ly/static/example-assets/sourceset/1x.mp4")!, width: 1920, height: 1080 ), ]) try await engine.block.addVideoFileURIToSourceSet( videoFill, property: "fill/video/sourceSet", uri: URL(string: "https://img.ly/static/example-assets/sourceset/2x.mp4")! ) ``` Exploration - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-exploration?language=swift&platform=ios#setup # Exploration Learn how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to explore scenes through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` public func findAll() -> [DesignBlockID] ``` Return all blocks currently known to the engine. * Returns: A list of block ids. ``` let allIds = engine.block.findAll() ``` ``` public func findAllPlaceholders() -> [DesignBlockID] ``` Return all placeholder blocks in the current scene. * Returns: A list of block ids. ``` let allPlaceholderIds = engine.block.findAllPlaceholders() ``` ``` public func find(byType type: DesignBlockType) throws -> [DesignBlockID] ``` Finds all blocks with the given type. * `type:`: The type to search for. * Returns: A list of block ids. ``` let allPages = try engine.block.find(byType: .page) ``` ``` public func find(byType type: FillType) throws -> [DesignBlockID] ``` Finds all blocks with the given type. * `type:`: The type to search for. * Returns: A list of block ids. ``` let allImageFills = try engine.block.find(byType: .image) ``` ``` public func find(byType type: ShapeType) throws -> [DesignBlockID] ``` Finds all blocks with the given type. * `type:`: The type to search for. * Returns: A list of block ids. ``` let allStarShapes = try engine.block.find(byType: .star) ``` ``` public func find(byType type: EffectType) throws -> [DesignBlockID] ``` Finds all blocks with the given type. * `type:`: The type to search for. * Returns: A list of block ids. ``` let allHalfToneEffects = try engine.block.find(byType: .halfTone) ``` ``` public func find(byType type: BlurType) throws -> [DesignBlockID] ``` Finds all blocks with the given type. * `type:`: The type to search for. * Returns: A list of block ids. ``` let allUniformBlurs = try engine.block.find(byType: .uniform) ``` ``` public func find(byKind kind: String) throws -> [DesignBlockID] ``` Finds all blocks with the given kind. * `kind:`: The kind to search for. * Returns: A list of block ids. ``` let allStickers = try engine.block.find(byKind: "sticker") ``` ``` public func find(byName name: String) -> [DesignBlockID] ``` Finds all blocks with the given name. * `name:`: The name to search for. * Returns: A list of block ids. ``` let ids = engine.block.find(byName: "someName") ``` How to use custom LUT filter - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/custom-lut-filter?language=swift&platform=ios#creating-custom-filters # How to use custom LUT filter ## Creating Custom Filters We use a technology called Lookup Tables (LUTs) in order to add new filters to our SDK. The main idea is that colors respond to operations that are carried out during the filtering process. We 'record' that very response by applying the filter to the identity image shown below. ![Identity LUT](/docs/cesdk/7d624d6315581dca5652b65884e5e117/identity.png) The resulting image can be used within our SDK and the recorded changes can then be applied to any image by looking up the transformed colors in the modified LUT. If you want to create a new filter, you'll need to [download](/docs/cesdk/images/identity.png) the identity LUT shown above, load it into an image editing software of your choice, apply your operations, save it and add it to your app. > **WARNING:** As any compression artifacts in the edited LUT could lead to distorted results when applying the filter, you need to save your LUT as a PNG file. ## Using Custom Filters In this example, we will use a hosted CDN LUT filter file. First we will load one of our demo scenes and change the first image to use LUT filter we will provide. We will also configure the necessary setting based on the file. LUT file we will use: ![](https://cdn.img.ly/assets/v1/ly.img.filter.lut/LUTs/imgly_lut_ad1920_5_5_128.png) Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/engine-guides-custom-lut-filter/CustomLUTFilter.swift) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/engine-guides-custom-lut-filter/CustomLUTFilter.swift) ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. ## Load Scene After the setup, we create a new scene. Within this scene, we create a page, set its dimensions, and append it to the scene. Lastly, we adjust the zoom level to properly fit the page into the view. ``` let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 100) try engine.block.setHeight(page, value: 100) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: scene, paddingLeft: 40.0, paddingTop: 40.0, paddingRight: 40.0, paddingBottom: 40.0) ``` ## Create Rectangle Next, we create a rectangle with defined dimensions and append it to the page. We will apply our LUT filter to this rectangle. ``` let rect = try engine.block.create(.graphic) try engine.block.setShape(rect, shape: engine.block.createShape(.rect)) try engine.block.setWidth(rect, value: 100) try engine.block.setHeight(rect, value: 100) try engine.block.appendChild(to: page, child: rect) ``` ## Load Image After creating the rectangle, we create an image fill with a specified URL. This will load the image as a fill for the rectangle to which we will apply the LUT filter. ``` let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg" ) ``` ## Create LUT Filter Now, we create a Look-Up Table (LUT) filter effect. We enable the filter, set its intensity, and provide a URL for the LUT file. We also define the tile count for the filter. The LUT filter effect is then applied to the rectangle and image should appear black and white. ``` let lutFilter = try engine.block.createEffect(.lutFilter) try engine.block.setBool(lutFilter, property: "effect/enabled", value: true) try engine.block.setFloat(lutFilter, property: "effect/lut_filter/intensity", value: 0.9) try engine.block.setString( lutFilter, property: "effect/lut_filter/lutFileURI", // swiftlint:disable:next line_length value: "https://cdn.img.ly/packages/imgly/cesdk-js/1.26.0/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png" ) try engine.block.setInt(lutFilter, property: "effect/lut_filter/verticalTileCount", value: 5) try engine.block.setInt(lutFilter, property: "effect/lut_filter/horizontalTileCount", value: 5) ``` ## Apply LUT Filter Finally, we apply the LUT filter effect to the rectangle, and set the image fill to the rectangle. Before setting an image fill, we destroy the default rectangle fill. ``` try engine.block.appendEffect(rect, effectID: lutFilter) try engine.block.setFill(rect, fill: imageFill) ``` Use of Emojis in Text - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/text-with-emojis?language=swift&platform=ios#setup # Use of Emojis in 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. Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-text-with-emojis/TextWithEmojis.swift) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-text-with-emojis/TextWithEmojis.swift) ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. ``` 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) ``` ## Change the Default Emoji Font The default front 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 the font loaded from our [CDN](https://cdn.img.ly/assets/v2/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/v2/emoji/LICENSE.txt). In order to change the URI, call the `func setSettingString(_ keypath: String, value: String) throws` [Editor API](/docs/cesdk/engine/api/editor-change-settings/) with "defaultEmojiFontFileUri" as keypath and the new URI as value. ``` 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/v3/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. ``` 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) ``` Configure Theming - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/mobile-editor/configuration/theming/?platform=ios&language=swift # Configure Theming 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](/docs/cesdk/mobile-editor/solutions/). Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-configuration-theming/ThemingEditorSolution.swift). * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-configuration-theming/ThemingEditorSolution.swift) ## Modifiers After initializing an editor SwiftUI view you can apply any SwiftUI _modifier_ to customize it like for any other SwiftUI view. ``` DesignEditor(settings) ``` 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. ``` .preferredColorScheme(colorScheme == .dark ? .light : .dark) ``` Integrate the Mobile Camera - CE.SDK | IMG.LY Docs [ios/swiftui/swift] https://img.ly/docs/cesdk/mobile-camera/quickstart/?platform=ios&language=swift&framework=swiftui # Integrate the Mobile Camera In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s mobile camera in your iOS app. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/camera-guides-quickstart/). * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/camera-guides-quickstart/) ### Requirements The mobile camera requires iOS 16 and Swift 5.10 (Xcode 15.4) or later. ### Using Swift Package Manager If you use [Swift Package Manager](https://github.com/apple/swift-package-manager) to build your app and want to integrate the Creative Engine and UI modules using your regular workflows, add the [IMGLYUI Swift Package](https://github.com/imgly/IMGLYUI-swift) as a dependency to your project. ![](/docs/cesdk/3c5227dab88fd8e5a0ca5fb8deeb16e1/spm-ui.png) This package provides multiple library products. Add the default `IMGLYUI` library to your app target to add all available UI modules included in this package to your app. To keep your app size minimal, only add the library product that you need, e.g., only add the `IMGLYCamera` library if you need to `import IMGLYCamera` in your code. On the _General_ page of your app target's Xcode project settings the _Frameworks, Libraries, and Embedded Content_ section lists all used library products. ## Usage This example shows the basic usage of the camera. ### Import You can get started right away by importing the camera module into your own code. ``` import IMGLYCamera ``` In this integration example, the camera is presented as a modal view after tapping a button. ``` Button("Use the Camera") { isPresented = true } ``` ### Initialization The camera is initialized with `EngineSettings`. You need to provide the license key that you received from IMG.LY. Optionally, you can provide a unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU) and it is especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. ``` Camera(.init(license: secrets.licenseKey, userID: "")) { result in ``` ### Result The `Camera`’s `onDismiss` closure returns a `Result`. If the user has recorded videos, the `.success(_)` case contains the `CameraResult`. ``` switch result { case let .success(.recording(recordings)): let urls = recordings.flatMap { $0.videos.map(\.url) } let recordedVideos = urls // Do something with the recorded videos print(recordedVideos) case .success(.reaction): print("Reaction case not handled here") case let .failure(error): print(error.localizedDescription) isPresented = false } ``` ### Environment The camera is best opened in a [`fullScreenCover`](https://developer.apple.com/documentation/swiftui/view/fullscreencover\(ispresented:ondismiss:content:\)). ``` .fullScreenCover(isPresented: $isPresented) { ``` Hierarchies - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-hierarchies?language=swift&platform=ios#setup # Hierarchies In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify the hierarchy of blocks through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Manipulate the hierarchy of blocks Only blocks that are direct or indirect children of a `page` block are rendered. Scenes without any `page` child may not be properly displayed by the CE.SDK editor. ``` public func getParent(_ id: DesignBlockID) throws -> DesignBlockID? ``` Query a block's parent. * `id:`: The block to query. * Returns: The parent's handle or `nil` if the block has no parent. ``` let parent = try engine.block.getParent(block) ``` ``` public func getChildren(_ id: DesignBlockID) throws -> [DesignBlockID] ``` Get all children of the given block. Children are sorted in their rendering order: Last child is rendered in front of other children. * `id:`: The block to query. * Returns: A list of block ids. ``` let childIds = try engine.block.getChildren(block) ``` ``` public func insertChild(into parent: DesignBlockID, child: DesignBlockID, at index: Int) throws ``` Insert a new or existing child at a certain position in the parent's children. Required scope: "editor/add" * `parent`: The block whose children should be updated. * `child`: The child to insert. Can be an existing child of `parent`. * `index`: The index to insert or move to. ``` try engine.block.insertChild(into: page, child: block, at: 0) ``` ``` public func appendChild(to parent: DesignBlockID, child: DesignBlockID) throws ``` Appends a new or existing child to a block's children. Required scope: "editor/add" * `parent`: The block whose children should be updated. * `child`: The child to insert. Can be an existing child of `parent`. ``` try engine.block.appendChild(to: parent!, child: block) ``` When adding a block to a new parent, it is automatically removed from its previous parent. Blur - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-blur?language=swift&platform=ios#setup # Blur In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a block's blur through the `block` API. Blurs can be added to any shape or page. You can create and configure individual blurs and apply them to blocks. Blocks support at most one blur at a time. The same blur may be used for different blocks at the same time. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Creating a Blur To create a blur, simply use `createBlur`. ``` public func createBlur(_ type: BlurType) throws -> DesignBlockID ``` Create a new blur. * `type:`: The type of the blur object that shall be created. * Returns: The created blur's handle. We currently support the following blur types: * `BlurType.uniform` * `BlurType.linear` * `BlurType.mirrored` * `BlurType.radial` ``` let radialBlur = try engine.block.createBlur(.radial) ``` ## Configuring a Blur You can configure blurs just like you configure design blocks. See [Modify Properties](/docs/cesdk/engine/api/block-properties/) for more detail. ``` try engine.block.setFloat(radialBlur, property: "radial/radius", value: 100) ``` ## Functions ``` public func setBlur(_ id: DesignBlockID, blurID: DesignBlockID) throws ``` Connects `block`'s blur to the given `blur` block. Required scope: "appearance/blur" * `id`: The block to update. * `blurID`: A 'blur' block. ``` try engine.block.setBlur(block, blurID: radialBlur) ``` ``` public func setBlurEnabled(_ id: DesignBlockID, enabled: Bool) throws ``` Enable or disable the blur of the given design block. * `id`: The block to update. * `enabled`: The new enabled value. ``` try engine.block.setBlurEnabled(block, enabled: true) ``` ``` public func isBlurEnabled(_ id: DesignBlockID) throws -> Bool ``` Query if blur is enabled for the given block. * `id:`: The block to query. * Returns: `true`, if the blur is enabled. `false` otherwise. ``` let isBlurEnabled = try engine.block.isBlurEnabled(block) ``` ``` public func supportsBlur(_ id: DesignBlockID) throws -> Bool ``` Checks whether the block supports blur. * `id:`: The block to query. * Returns: `true`, if the block supports blur. ``` if try engine.block.supportsBlur(block) { ``` ``` public func getBlur(_ id: DesignBlockID) throws -> DesignBlockID ``` Get the 'blur' block of the given design block. * `id:`: The block to query. * Returns: The 'blur' block. ``` let existingBlur = try engine.block.getBlur(block) ``` Shapes - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-shapes?language=swift&platform=ios#setup # Shapes In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a `graphic` block's shape through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Creating a Shape To create a shape simply use `createShape(type: string): number`. ``` let star = try engine.block.createShape(.star) ``` ``` public func createShape(_ type: ShapeType) throws -> DesignBlockID ``` Create a new shape. * `type:`: The type of the shape object that shall be created. * Returns: The created shape's handle. ``` let star = try engine.block.createShape(.star) ``` We currently support the following shape types: * `ShapeType.rect` * `ShapeType.line` * `ShapeType.ellipse` * `ShapeType.polygon` * `ShapeType.star` * `ShapeType.vectorPath` ``` let star = try engine.block.createShape(.star) ``` ## Functions You can configure shapes just like you configure design blocks. See [Modify Properties](/docs/cesdk/engine/api/block-properties/) for more detail. ``` try engine.block.setInt(star, property: "shape/star/points", value: 6) ``` ``` public func getShape(_ id: DesignBlockID) throws -> DesignBlockID ``` Returns the block containing the shape properties of the given block. * `id:`: The block whose shape block should be returned. * Returns: The block that currently defines the given block's shape. ``` let previousShape = try engine.block.getShape(block) ``` ## Destroying and Replacing Shapes Remember to first destroy the previous shape if you don't need it any more before changing the shape of a design block. ``` try engine.block.destroy(previousShape) ``` ``` public func setShape(_ id: DesignBlockID, shape: DesignBlockID) throws ``` Sets the block containing the shape properties of the given block. Note that the previous shape block is not destroyed automatically. Required scope: "shape/change" * `id`: The block whose shape should be changed. * `shape`: The new shape. ``` try engine.block.setShape(block, shape: star) ``` ``` public func supportsShape(_ id: DesignBlockID) throws -> Bool ``` Query if the given block has a shape property. * `id:`: The block to query. * Returns: `true`, if the block has a shape property, an error otherwise. ``` try engine.block.supportsShape(block) ``` Observe Editing State - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/editor-state?language=swift&platform=ios#setup # Observe Editing State In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to set and query the editor state in the `editor` API, i.e., what type of content the user is currently able to edit. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## State The editor state consists of the current edit mode, which informs what type of content the user is currently able to edit. The edit mode can be set to either `'Transform'`, `'Crop'`, `'Text'`, or a custom user-defined one. You can also query the intended mouse cursor and the location of the text cursor while editing text. Instead of having to constantly query the state in a loop, you can also be notified when the state has changed to then act on these changes in a callback. ``` public var onStateChanged: AsyncStream { get } ``` Subscribe to changes to the editor state. ``` let task = Task { for await _ in engine.editor.onStateChanged { print("Editor state has changed") } } ``` ``` public func setEditMode(_ mode: EditMode) ``` Set the edit mode of the editor. An edit mode defines what type of content can currently be edited by the user. * Note: The initial edit mode is "Transform". * `mode:`: "Transform", "Crop", "Text", "Playback". ``` engine.editor.setEditMode(.crop) ``` ``` public func getEditMode() -> EditMode ``` Get the current edit mode of the editor. An edit mode defines what type of content can currently be edited by the user. * Returns: "Transform", "Crop", "Text", "Playback". ``` engine.editor.getEditMode() // 'Crop' ``` ``` public func unstable_isInteractionHappening() throws -> Bool ``` If an user interaction is happening, e.g., a resize edit with a drag handle or a touch gesture. * Returns: true if an interaction is happening. ``` engine.editor.unstable_isInteractionHappening(); ``` ## Cursor ``` public func getCursorType() -> CursorType ``` Get the type of cursor that should be displayed by the application. * Returns: The cursor type. ``` engine.editor.getCursorType() ``` ``` public func getCursorRotation() -> Float ``` Get the rotation with which to render the mouse cursor. * Returns: The angle in radians. ``` engine.editor.getCursorRotation() ``` ``` public func getTextCursorPositionInScreenSpaceX() -> Float ``` Get the current text cursor's x position in screen space. * Returns: The text cursor's x position in screen space. ``` engine.editor.getTextCursorPositionInScreenSpaceX() ``` ``` public func getTextCursorPositionInScreenSpaceY() -> Float ``` Get the current text cursor's y position in screen space. * Returns: The text cursor's y position in screen space. ``` engine.editor.getTextCursorPositionInScreenSpaceY() ``` How to Use Fills - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/using-fills?language=swift&platform=ios#setup # How to Use Fills Some [design blocks](/docs/cesdk/engine/guides/blocks/) in CE.SDK allow you to modify or replace their fill. The fill is an object that defines the contents within the shape of a block. CreativeEditor SDK supports many different types of fills, such as images, solid colors, gradients and videos. Similarly to blocks, each fill has a numeric id which can be used to query and [modify its properties](/docs/cesdk/engine/api/block-properties/). Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-using-fills/UsingFills.swift) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-using-fills/UsingFills.swift) ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. ``` let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 100) try engine.block.setFill(block, fill: engine.block.createFill(.color)) try engine.block.appendChild(to: page, child: block) ``` ## Accessing Fills Not all types of design blocks support fills, so you should always first call the `func supportsFill(_ id: DesignBlockID) throws -> Bool` API before accessing any of the following APIs. ``` try engine.block.supportsFill(scene) // Returns false try engine.block.supportsFill(block) // Returns true ``` In order to receive the fill id of a design block, call the `func getFill(_ id: DesignBlockID) throws -> DesignBlockID` API. You can now pass this id into other APIs in order to query more information about the fill, e.g. its type via the `func getType(_ id: DesignBlockID) throws -> String` API. ``` let colorFill = try engine.block.getFill(block) let defaultRectFillType = try engine.block.getType(colorFill) ``` ## Fill Properties Just like design blocks, fills with different types have different properties that you can query and modify via the API. Use `func findAllProperties(_ id: DesignBlockID) throws -> [String]` in order to get a list of all properties of a given fill. For the solid color fill in this example, the call would return `["fill/color/value", "type"]`. Please refer to the [API docs](/docs/cesdk/engine/api/block-fill-types/) for a complete list of all available properties for each type of fill. ``` let allFillProperties = try engine.block.findAllProperties(colorFill) ``` Once we know the property keys of a fill, we can use the same APIs as for design blocks in order to modify those properties. For example, we can use `func setColor(_ id: DesignBlockID, property: String, color: Color) throws` in order to change the color of the fill to red. Once we do this, our graphic block with rect shape will be filled with solid red. ``` try engine.block.setColor(colorFill, property: "fill/color/value", color: .rgba(r: 1.0, g: 0.0, b: 0.0, a: 1.0)) ``` ## Disabling Fills You can disable and enable a fill using the `func setFillEnabled(_ id: DesignBlockID, enabled: Bool) throws` API, for example in cases where the design block should only have a stroke but no fill. Notice that you have to pass the id of the design block and not of the fill to the API. ``` try engine.block.setFillEnabled(block, enabled: false) try engine.block.setFillEnabled(block, enabled: !engine.block.isFillEnabled(block)) ``` ## Changing Fill Types All design blocks that support fills allow you to also exchange their current fill for any other type of fill. In order to do this, you need to first create a new fill object using `func createFill(_ type: FillType) throws -> DesignBlockID`. We currently support the following fill types: * `FillType.color` * `FillType.linearGradient` * `FillType.radialGradient` * `FillType.conicalGradient` * `FillType.image` * `FillType.video` * `FillType.pixelStream` ``` let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg" ) ``` In order to assign a fill to a design block, simply call `func setFill(_ id: DesignBlockID, fill: DesignBlockID) throws`. Make sure to delete the previous fill of the design block first if you don't need it any more, otherwise we will have leaked it into the scene and won't be able to access it any more, because we don't know its id. Notice that we don't use the `appendChild` API here, which only works with design blocks and not fills. When a fill is attached to one design block, it will be automatically destroyed when the block itself gets destroyed. ``` try engine.block.destroy(colorFill) try engine.block.setFill(block, fill: imageFill) /* The following line would also destroy imageFill */ // try engine.block.destroy(circle) ``` ## Duplicating Fills If we duplicate a design block with a fill that is only attached to this block, the fill will automatically be duplicated as well. In order to modify the properties of the duplicate fill, we have to query its id from the duplicate block. ``` let duplicateBlock = try engine.block.duplicate(block) try engine.block.setPositionX(duplicateBlock, value: 450) let autoDuplicateFill = try engine.block.getFill(duplicateBlock) try engine.block.setString( autoDuplicateFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_2.jpg" ) // let manualDuplicateFill = try engine.block.duplicate(autoDuplicateFill) // /* We could now assign this fill to another block. */ // try engine.block.destroy(manualDuplicateFill) ``` ## Sharing Fills It is also possible to share a single fill instance between multiple design blocks. In that case, changing the properties of the fill will apply to all of the blocks that it's attached to at once. Destroying a block with a shared fill will not destroy the fill until there are no other design blocks left that still use that fill. ``` let sharedFillBlock = try engine.block.create(.graphic) try engine.block.setShape(sharedFillBlock, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(sharedFillBlock, value: 350) try engine.block.setPositionY(sharedFillBlock, value: 400) try engine.block.setWidth(sharedFillBlock, value: 100) try engine.block.setHeight(sharedFillBlock, value: 100) try engine.block.appendChild(to: page, child: sharedFillBlock) try engine.block.setFill(sharedFillBlock, fill: engine.block.getFill(block)) ``` Layout - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-layout?language=swift&platform=ios#setup # Layout In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify scenes layout through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Layout of Blocks #### Note on layout and frame size The frame size is determined during the layout phase of the render process inside the engine. This means that calling `getFrameSize()` immediately after modifying the scene might return an inaccurate result. The CreativeEngine supports three different modes for positioning blocks. These can be set for each block and both coordinates independently: * `'Absolute'`: the position value is interpreted in the scene's current design unit. * `'Percent'`: the position value is interpreted as percentage of the block's parent's size, where 1.0 means 100%. * `'Auto'` : the position is automatically determined. Likewise there are also three different modes for controlling a block's size. Again both dimensions can be set independently: * `'Absolute'`: the size value is interpreted in the scene's current design unit. * `'Percent'`: the size value is interpreted as percentage of the block's parent's size, where 1.0 means 100%. * `'Auto'` : the block's size is automatically determined by the size of the block's content. ### Positioning ``` public func getPositionX(_ id: DesignBlockID) throws -> Float ``` Query a block's x position. * `id:`: The block to query. * Returns: The value of the x position. ``` let x = try engine.block.getPositionX(block) ``` ``` public func getPositionY(_ id: DesignBlockID) throws -> Float ``` Query a block's y position. * `id:`: The block to query. * Returns: The value of the y position. ``` let y = try engine.block.getPositionY(block) ``` ``` public func getPositionXMode(_ id: DesignBlockID) throws -> PositionMode ``` Query a block's mode for its x position. * `id:`: The block to query. * Returns: The current mode for the x position: absolute, percent or undefined. ``` let xMode = try engine.block.getPositionXMode(block) ``` ``` public func getPositionYMode(_ id: DesignBlockID) throws -> PositionMode ``` Query a block's mode for its y position. * `id:`: The block to query. * Returns: The current mode for the y position: absolute, percent or undefined. ``` let yMode = try engine.block.getPositionYMode(block) ``` ``` public func setPositionX(_ id: DesignBlockID, value: Float) throws ``` Update a block's x position. The position refers to the block's local space, relative to its parent with the origin at the top left. Required scope: "layer/move" * `id`: The block to update. * `value`: The value of the x position. ``` try engine.block.setPositionX(block, value: 0.25) ``` ``` public func setPositionY(_ id: DesignBlockID, value: Float) throws ``` Update a block's y position. The position refers to the block's local space, relative to its parent with the origin at the top left. Required scope: "layer/move" * `id`: The block to update. * `value`: The value of the y position. ``` try engine.block.setPositionY(block, value: 0.25) ``` ``` public func setPositionXMode(_ id: DesignBlockID, mode: PositionMode) throws ``` Set a block's mode for its x position. Required scope: "layer/move" * `id`: The block to update. * `mode`: The x position mode: absolute, percent or undefined. ``` try engine.block.setPositionXMode(block, mode: .percent) ``` ``` public func setPositionYMode(_ id: DesignBlockID, mode: PositionMode) throws ``` Set a block's mode for its y position. Required scope: "layer/move" * `id`: The block to update. * `mode`: The y position mode: absolute, percent or undefined. ``` try engine.block.setPositionYMode(block, mode: .percent) ``` ### Layers ``` public func setAlwaysOnTop(_ id: DesignBlockID, enabled: Bool) throws ``` Set a block to be always-on-top. If true, this blocks's global sorting order is automatically adjusted to be higher than all other siblings without this property. If more than one block is set to be always-on-top, the child order decides which is on top. * `id`: The block to update. * `enabled`: The new state. ``` try engine.block.setAlwaysOnTop(block, enabled: false) ``` ``` public func isAlwaysOnTop(_ id: DesignBlockID) throws -> Bool ``` If a block is set to be always-on-top. * `id`: The block to query. ``` let isAlwaysOnTop = try engine.block.isAlwaysOnTop(block) ``` ``` public func setAlwaysOnBottom(_ id: DesignBlockID, enabled: Bool) throws ``` Set a block to be always-on-bottom. If true, this blocks's global sorting order is automatically adjusted to be lower than all other siblings without this property. If more than one block is set to be always-on-bottom, the child order decides which is on the bottom. * `id`: The block to update. * `enabled`: The new state. ``` try engine.block.setAlwaysOnBottom(block, enabled: false) ``` ``` public func isAlwaysOnBottom(_ id: DesignBlockID) throws -> Bool ``` If a block is set to be always-on-bottom. * `id`: The block to query. ``` let isAlwaysOnBottom = try engine.block.isAlwaysOnBottom(block) ``` ``` public func bringToFront(_ id: DesignBlockID) throws ``` Updates the sorting order of this block and all of its manually created siblings so that the given block has the highest sorting order. If the block is parented to a track, it is first moved up in the hierarchy. * `id`: The block to update. ``` try engine.block.bringToFront(block) ``` ``` public func sendToBack(_ id: DesignBlockID) throws ``` Updates the sorting order of this block and all of its manually created siblings so that the given block has the lowest sorting order. If the block is parented to a track, it is first moved up in the hierarchy. * `id`: The block to update. ``` try engine.block.sendToBack(block) ``` ``` public func bringForward(_ id: DesignBlockID) throws ``` Updates the sorting order of this block and all of its superjacent siblings so that the given block has a higher sorting order than the next superjacent sibling. If the block is parented to a track, it is first moved up in the hierarchy. * `id`: The block to update. ``` try engine.block.bringForward(block) ``` ``` public func sendBackward(_ id: DesignBlockID) throws ``` Updates the sorting order of this block and all of its manually created and subjacent siblings so that the given block will have a lower sorting order than the next subjacent sibling. If the block is parented to a track, it is first moved up in the hierarchy. * `id`: The block to update. ``` try engine.block.sendBackward(block) ``` ### Size ``` public func getWidth(_ id: DesignBlockID) throws -> Float ``` Query a block's width. * `id:`: The block to query. * Returns: The value of the block's width. ``` let width = try engine.block.getWidth(block) ``` ``` public func getWidthMode(_ id: DesignBlockID) throws -> SizeMode ``` Query a block's mode for its width. * `id:`: The block to query. * Returns: The current mode for the width: absolute, percent or auto. ``` let widthMode = try engine.block.getWidthMode(block) ``` ``` public func getHeight(_ id: DesignBlockID) throws -> Float ``` Query a block's height. * `id:`: The block to query. * Returns: The value of the block's height. ``` let height = try engine.block.getHeight(block) ``` ``` public func getHeightMode(_ id: DesignBlockID) throws -> SizeMode ``` Query a block's mode for its height. * `id:`: The block to query. * Returns: The current mode for the height: absolute, percent or auto. ``` let heightMode = try engine.block.getHeightMode(block) ``` ``` public func setWidth(_ id: DesignBlockID, value: Float, maintainCrop: Bool = false) throws ``` Update a block's width and optionally maintain the crop. If the crop is maintained, the crop values will be automatically adjusted. The content fill mode `Cover` is only kept if the `features/transformEditsRetainCoverMode` setting is enabled, otherwise it will change to `Crop`. Required scope: "layer/resize" * `id`: The block to update. * `value`: The new width of the block. * `maintainCrop`: Whether or not the crop values, if available, should be automatically adjusted. ``` try engine.block.setWidth(block, value: 0.5) try engine.block.setWidth(block, value: 2.5, maintainCrop: true) ``` ``` public func setWidthMode(_ id: DesignBlockID, mode: SizeMode) throws ``` Set a block's mode for its width. Required scope: "layer/resize" * `id`: The block to update. * `mode`: The width mode. ``` try engine.block.setWidthMode(block, mode: .percent) ``` ``` public func setHeight(_ id: DesignBlockID, value: Float, maintainCrop: Bool = false) throws ``` Update a block's height and optionally maintain the crop. If the crop is maintained, the crop values will be automatically adjusted. The content fill mode `Cover` is only kept if the `features/transformEditsRetainCoverMode` setting is enabled, otherwise it will change to `Crop`. Required scope: "layer/resize" * `id`: The block to update. * `value`: The new height of the block. * `maintainCrop`: Whether or not the crop values, if available, should be automatically adjusted. features/transformEditsRetainCoverMode is enabled. ``` try engine.block.setHeight(block, value: 0.5) try engine.block.setHeight(block, value: 2.5, maintainCrop: true) ``` ``` public func setHeightMode(_ id: DesignBlockID, mode: SizeMode) throws ``` Set a block's mode for its height. Required scope: "layer/resize" * `id`: The block to update. * `mode`: The height mode. ``` try engine.block.setHeightMode(block, mode: .percent) ``` ### Rotation ``` public func getRotation(_ id: DesignBlockID) throws -> Float ``` Query a block's rotation in radians. * `id:`: The block to query. * Returns: The block's rotation around its center in radians. ``` let rad = try engine.block.getRotation(block) ``` ``` public func setRotation(_ id: DesignBlockID, radians: Float) throws ``` Update a block's rotation. Required scope: "layer/rotate" * `id`: The block to update. * `radians`: The new rotation in radians. Rotation is applied around the block's center. ``` try engine.block.setRotation(block, radians: .pi) ``` ### Flipping ``` public func setFlipHorizontal(_ id: DesignBlockID, flip: Bool) throws ``` Update a block's horizontal flip. Required scope: "layer/flip" * `id`: The block to update. * `flip`: If the flip should be enabled. ``` try engine.block.setFlipHorizontal(block, flip: true) ``` ``` public func getFlipHorizontal(_ id: DesignBlockID) throws -> Bool ``` Query a block's horizontal flip state. * `id:`: The block to query. * Returns: A boolean indicating for whether the block is flipped in the queried direction. ``` let flipHorizontal = try engine.block.getFlipHorizontal(block) ``` ``` public func setFlipVertical(_ id: DesignBlockID, flip: Bool) throws ``` Update a block's vertical flip. Required scope: "layer/flip" * `id`: The block to update. * `flip`: If the flip should be enabled. ``` try engine.block.setFlipVertical(block, flip: false) ``` ``` public func getFlipVertical(_ id: DesignBlockID) throws -> Bool ``` Query a block's vertical flip state. * `id:`: The block to query. * Returns: A boolean indicating for whether the block is flipped in the queried direction. ``` let flipVertical = try engine.block.getFlipVertical(block) ``` ### Scaling ``` public func scale(_ id: DesignBlockID, to scale: Float, anchorX: Float = 0, anchorY: Float = 0) throws ``` Scales the block and all of its children proportionally around the specified relative anchor point. This updates the position, size and style properties (e.g. stroke width) of the block and its children. Required scope: "layer/resize" * `id`: The block that should be scaled. * `scale`: The scale factor to be applied to the current properties of the block. * `anchorX`: The relative position along the width of the block around which the scaling should occur. (0 = left edge, 0.5 = center, 1 = right edge) * `anchorY`: The relative position along the height of the block around which the scaling should occur. (0 = top edge, 0.5 = center, 1 = bottom edge) ``` try engine.block.scale(block, to: 2.0, anchorX: 0.5, anchorY: 0.5) ``` ### Fill a Block's Parent ``` public func fillParent(_ id: DesignBlockID) throws ``` Resize and position a block to entirely fill its parent block. Required scope: "layer/move" * "layer/resize" * `id:`: The block that should fill its parent. ``` try engine.block.fillParent(block) ``` ### Resize Blocks Content-aware ``` public func resizeContentAware(_ ids: [DesignBlockID], width: Float, height: Float) throws ``` Resize all blocks to the given size. The content of the blocks is automatically adjusted to fit the new dimensions. Required scope: "layer/resize" * `ids`: The blocks to resize. * `width`: The new width of the blocks. * `height`: The new height of the blocks. * Returns: An error if the blocks could not be resized. ``` let pages = try engine.scene.getPages() try engine.block.resizeContentAware(pages, width: 100.0, height: 100.0) ``` ### Even Distribution ``` public func isDistributable(_ ids: [DesignBlockID]) throws -> Bool ``` Confirms that a given set of blocks can be distributed. * `ids:`: A non-empty array of block ids. * Returns: Whether the blocks can be distributed. ``` if try engine.block.isDistributable([member1, member2]) { ``` ``` public func distributeHorizontally(_ ids: [DesignBlockID]) throws ``` Distribute multiple blocks horizontally within their bounding box so that the space between them is even. Required scope: "layer/move" * `ids:`: A non-empty array of block ids. ``` try engine.block.distributeHorizontally([member1, member2]) ``` ``` public func distributeVertically(_ ids: [DesignBlockID]) throws ``` Distribute multiple blocks vertically within their bounding box so that the space between them is even. Required scope: "layer/move" * `ids:`: A non-empty array of block ids. ``` try engine.block.distributeVertically([member1, member2]) ``` ### Alignment ``` public func isAlignable(_ ids: [DesignBlockID]) throws -> Bool ``` Confirms that a given set of blocks can be aligned. * `ids:`: A non-empty array of block ids. * Returns: Whether the blocks can be aligned. ``` if try engine.block.isAlignable([member1, member2]) { ``` ``` public func alignHorizontally(_ ids: [DesignBlockID], alignment: HorizontalBlockAlignment) throws ``` Align multiple blocks horizontally within their bounding box or a single block to its parent. Required scope: "layer/move" * `ids:`: A non-empty array of block ids. * `alignment:`: How they should be aligned: left, right, or center ``` try engine.block.alignHorizontally([member1, member2], alignment: .left) ``` ``` public func alignVertically(_ ids: [DesignBlockID], alignment: VerticalBlockAlignment) throws ``` Align multiple blocks vertically within their bounding box or a single block to its parent. Required scope: "layer/move" * `ids:`: A non-empty array of block ids. * `alignment:`: How they should be aligned: top, bottom, or center ``` try engine.block.alignVertically([member1, member2], alignment: .top) ``` ### Computed Dimensions ``` public func getFrameX(_ id: DesignBlockID) throws -> Float ``` Get a block's layout position on the x-axis. The layout position is only available after an internal update loop, which may not happen immediately. * `id:`: The block to query. * Returns: The layout position on the x-axis. ``` let frameX = try engine.block.getFrameX(block) ``` ``` public func getFrameY(_ id: DesignBlockID) throws -> Float ``` Get a block's layout position on the y-axis. The layout position is only available after an internal update loop, which may not happen immediately. * `id:`: The block to query. * Returns: The layout position on the y-axis. ``` let frameY = try engine.block.getFrameY(block) ``` ``` public func getFrameWidth(_ id: DesignBlockID) throws -> Float ``` Get a block's layout width. The layout width is only available after an internal update loop, which may not happen immediately. * `id:`: The block to query. * Returns: The layout width. ``` let frameWidth = try engine.block.getFrameWidth(block) ``` ``` public func getFrameHeight(_ id: DesignBlockID) throws -> Float ``` Get a block's layout height. The layout height is only available after an internal update loop, which may not happen immediately. * `id:`: The block to query. * Returns: The layout height. ``` let frameHeight = try engine.block.getFrameHeight(block) ``` ``` public func getGlobalBoundingBoxX(_ id: DesignBlockID) throws -> Float ``` Get the x position of the block's axis-aligned bounding box in the scene's global coordinate space. The scene's global coordinate space has its origin at the top left. * `id:`: The block whose bounding box should be calculated. * Returns: The x coordinate of the position of the axis-aligned bounding box. ``` let globalX = try engine.block.getGlobalBoundingBoxX(block) ``` ``` public func getGlobalBoundingBoxY(_ id: DesignBlockID) throws -> Float ``` Get the y position of the block's axis-aligned bounding box in the scene's global coordinate space. The scene's global coordinate space has its origin at the top left. * `id:The`: block whose bounding box should be calculated. * Returns: The y coordinate of the position of the axis-aligned bounding box. ``` let globalY = try engine.block.getGlobalBoundingBoxY(block) ``` ``` public func getGlobalBoundingBoxWidth(_ id: DesignBlockID) throws -> Float ``` Get the width of the block's axis-aligned bounding box in the scene's global coordinate space. The scene's global coordinate space has its origin at the top left. * `id:`: The block whose bounding box should be calculated. * Returns: The width of the axis-aligned bounding box. ``` let globalWidth = try engine.block.getGlobalBoundingBoxWidth(block) ``` ``` public func getGlobalBoundingBoxHeight(_ id: DesignBlockID) throws -> Float ``` Get the height of the block's axis-aligned bounding box in the scene's global coordinate space. The scene's global coordinate space has its origin at the top left. * `id:`: The block whose bounding box should be calculated. * Returns: The height of the axis-aligned bounding box. ``` let globalHeight = try engine.block.getGlobalBoundingBoxHeight(block) ``` ``` public func getScreenSpaceBoundingBox(containing blocks: [DesignBlockID]) throws -> CGRect ``` Get the position and size of the axis-aligned bounding box for the given blocks in screen space. * `blocks:`: The blocks whose bounding box should be calculated. * Returns: The position and size of the bounding box as `CGRect` (in points). ``` let screenSpaceRect = try engine.block.getScreenSpaceBoundingBox(containing: [block]) ``` ### Transform Locking You can lock the transform of a block to prevent changes to any of its transformations. That is the block's position, rotation, scale, and sizing. ``` public func isTransformLocked(_ id: DesignBlockID) throws -> Bool ``` Query a block's transform locked state. If `true`, the block's transform can't be changed. * `id:`: The block to query. * Returns: `True` if transform locked, `false` otherwise. ``` let isTransformLocked = try engine.block.isTransformLocked(block) ``` ``` public func setTransformLocked(_ id: DesignBlockID, locked: Bool) throws ``` Update a block's transform locked state. * `id`: The block to update. * `locked`: Whether the block's transform should be locked. ``` try engine.block.setTransformLocked(block, locked: true) ``` How to Use Shapes - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/using-shapes?language=swift&platform=ios#setup # How to Use Shapes The `graphic` [design block](/docs/cesdk/engine/guides/blocks/) 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](/docs/cesdk/engine/api/block-properties/). Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-using-shapes/UsingShapes.swift) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-using-shapes/UsingShapes.swift) ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `engine.block`. Check out the [APIs Overview](/docs/cesdk/engine/api/) to see that illustrated in more detail. ``` 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) ``` ## 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. ``` try engine.block.supportsShape(graphic) // Returns true let text = try engine.block.create(.text) try engine.block.supportsShape(text) // Returns false ``` ## 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](/docs/cesdk/engine/guides/using-fills/). 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. We currently support the following shape types: * `ShapeType.rect` * `ShapeType.line` * `ShapeType.ellipse` * `ShapeType.polygon` * `ShapeType.star` * `ShapeType.vectorPath` ``` 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. ``` try engine.block.setShape(graphic, shape: rectShape) ``` 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. ``` 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. ``` 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](/docs/cesdk/engine/api/block-shape-types/) for a complete list of all available properties for each type of shape. ``` 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. ``` try engine.block.setInt(starShape, property: "shape/star/points", value: 6) ``` Load Scenes from a Remote URL - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/load-scene-from-url?language=swift&platform=ios#warning # Load Scenes from a Remote URL In this example, we will show you how to load scenes from a remote URL with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-load-scene-from-remote/LoadSceneFromRemote.swift) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-load-scene-from-remote/LoadSceneFromRemote.swift) Loading an existing scene allows resuming work on a previous session or adapting an existing template to your needs. This can be done by fetching a scene file or an archive file from a URL. #### Warning Saving a scene can be done as a either _scene file_ or as an _archive file_ (c.f. [Saving scenes](/docs/cesdk/engine/guides/save-scene/)). A _scene file_ does not include any fonts or images. Only the source URIs of assets, the general layout, and element properties are stored. When loading scenes in a new environment, ensure previously used asset URIs are available. Conversely, an _archive file_ contains within it the scene's assets and references them as relative URIs. Determine a URL that points to a scene binary string. ``` let sceneUrl = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! ``` We can then pass that string to the `func load(from url: URL) async throws -> DesignBlockID` function. The editor will reset and present the given scene to the user. The function is asynchronous and it does not throw if the scene load succeeded. ``` let scene = try await engine.scene.load(from: sceneUrl) ``` From this point on we can continue to modify our scene. In this example, assuming the scene contains a text element, we add a drop shadow to it. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` let text = try engine.block.find(byType: .text).first! try engine.block.setDropShadowEnabled(text, enabled: true) ``` Scene loads may be reverted using `engine.editor.undo()`. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). Using a custom URI resolver - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/resolve-custom-uri?language=swift&platform=ios#warning # Using a custom URI resolver CE.SDK gives you full control over how URIs should be resolved. To register a custom resolver, use `setURIResolver` and pass in a function implementing your resolution logic. If a custom resolver is set, any URI requested by the engine is passed through the resolver. The URI your logic returns is then fetched by the engine. The resolved URI is just used for the current request and not stored. If, and only if, no custom resolver is set, the engine performs the default behaviour: absolute paths are unchanged and relative paths are prepended with the value of the `basePath` setting. #### Warning Your custom URI resolver must return an URL. ​ Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-uri-resolver/URIResolver.swift) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-uri-resolver/URIResolver.swift) We can preview the effects of setting a custom URI resolver with the function `func getAbsoluteURI(relativePath: String) throws -> String`. Before setting a custom URI resolver, the default behavior is as before and the given relative path will be prepended with the contents of `basePath`. ``` // This will return "https://cdn.img.ly/packages/imgly/cesdk-js/1.10.0-preview.1/assets/banana.jpg" try engine.editor.getAbsoluteURI(relativePath: "/banana.jpg") ``` To show that the resolver can be fairly free-form, in this example we register a custom URI resolver that replaces all `.jpg` images with our company logo. The resolved URI are expected to be absolute. Note: you can still access the default URI resolver by calling `func defaultURIResolver(relativePath: String) -> String`. ``` // Replace all .jpg files with the IMG.LY logo! try engine.editor.setURIResolver { uri in if uri.hasSuffix(".jpg") { return URL(string: "https://img.ly/static/ubq_samples/imgly_logo.jpg")! } // Make use of the default URI resolution behavior. return URL(string: engine.editor.defaultURIResolver(relativePath: uri))! } ``` Given the same path as earlier, the custom resolver transforms it as specified. Note that after a custom resolver is set, relative paths that the resolver does not transform remain unmodified. ``` // The custom resolver will return a path to the IMG.LY logo because the given path ends with ".jpg". // This applies regardless if the given path is relative or absolute. try engine.editor.getAbsoluteURI(relativePath: "/banana.jpg") // The custom resolver will not modify this path because it ends with ".png". try engine.editor.getAbsoluteURI(relativePath: "https://example.com/orange.png") // Because a custom resolver is set, relative paths that the resolver does not transform remain unmodified! try engine.editor.getAbsoluteURI(relativePath: "/orange.png") ``` To remove a previously set custom resolver, call the function with a `nil` value. The URI resolution is now back to the default behavior. ``` // Removes the previously set resolver. try engine.editor.setURIResolver(nil) // Since we"ve removed the custom resolver, this will return // "https://cdn.img.ly/packages/imgly/cesdk-js/1.10.0-preview.1/assets/banana.jpg" like before. try engine.editor.getAbsoluteURI(relativePath: "/banana.jpg") ``` Get started with CE.SDK Engine - CE.SDK | IMG.LY Docs [ios/uikit/swift] https://img.ly/docs/cesdk/engine/quickstart/?platform=ios&language=swift&framework=uikit # Get started with CE.SDK Engine In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s engine in your iOS app. The engine enables you to power your own UI and creative workflows. Whether you want to keep your current UI or design a new one from scratch, our API can do the heavy lifting for you no matter what your starting point is. Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0) ### Requirements Creative Engine requires iOS 14 and Swift 5.10 (Xcode 15.4) or later. ### Using Swift Package Manager If you use [Swift Package Manager](https://github.com/apple/swift-package-manager) to build your app and want to integrate the Creative Engine module using your regular workflows, add the [IMGLYEngine Swift Package](https://github.com/imgly/IMGLYEngine-swift) as a dependency to your project. ![](/docs/cesdk/f791cde10b12204d724306eea14c9f99/spm-engine.png) ### Using Cocoapods Creative Engine is also available through [CocoaPods](https://cocoapods.org). To install it, simply add the following line to your Podfile: `pod 'IMGLYEngine'`. Then run `pod install --repo-update` to install the latest version of the SDK. ## Import You can get started right away by importing the CESDK module into your own code. ``` import IMGLYEngine ``` ## Initialization To initialize `IMGLYEngine` for UIKit create [`MTKView`](https://developer.apple.com/documentation/metalkit/mtkview) canvas instance and pass it to the awaitable `Engine(context: license: userID:)` initializer. The initializer is awaitable so we recommend to call the engine initialization from `viewDidAppear()` method as demonstrated below. ``` private var engine: Engine? private lazy var canvas = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice()) ``` You need to manually add the canvas to the view hierarchy and setup appropriate constraints. ``` view.addSubview(canvas) canvas.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ canvas.leftAnchor.constraint(equalTo: view.leftAnchor), canvas.rightAnchor.constraint(equalTo: view.rightAnchor), canvas.topAnchor.constraint(equalTo: view.topAnchor), canvas.bottomAnchor.constraint(equalTo: view.bottomAnchor), ]) ``` To pass view lifecycle events to the engine, use `viewDidAppear(_ animated: Bool)` and `viewWillDisappear(_ animated: Bool)` methods by calling `onAppear()` and `onDisappear()`. ``` override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) Task { engine = try await Engine( context: .metalView(view: canvas), license: secrets.licenseKey, userID: "" ) engine?.onAppear() spinner.stopAnimating() button.isHidden = false } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) engine?.onDisappear() } ``` ### Licensing In the awaitable initializer there are 2 arguments that are tied to your licensing. * `license` - an API key that you downloaded from our dashboard. * `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. If the license is invalid, the engine will not start and throw an error. ``` engine = try await Engine( context: .metalView(view: canvas), license: secrets.licenseKey, userID: "" ) ``` ## Use the Creative Engine After these setup steps, you can use our APIs to interact with the Creative Engine. [The next couple of pages](/docs/cesdk/engine/guides/) will document the methods available. ``` Task { @MainActor in 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) try engine.block.find(byType: .text).forEach { id in try engine.block.setOpacity(id, value: 0.5) } } ``` Managing Colors - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/colors?language=swift&platform=ios#rgb # Managing Colors When specifying a color property, you can use one of three color spaces: [RGB](https://en.wikipedia.org/wiki/RGB_color_model), [CMYK](https://en.wikipedia.org/wiki/CMYK_color_model) and [spot color](https://en.wikipedia.org/wiki/Spot_color). The following properties can be set with the function `setColor` and support all three color spaces: * `'backgroundColor/color'` * `'camera/clearColor'` * `'dropShadow/color'` * `'fill/color/value'` * `'stroke/color'` ### RGB RGB is the color space used when rendering a color to a screen. All values of `R`, `G`, `B`must be between `0.0` and `1.0` When using RGB, you typically also specify opacity or alpha as a value between `0.0` and `1.0` and is then referred to as `RGBA`. When a RGB color has an alpha value that is not `1.0`, it will be rendered to screen with corresponding transparency. ### CMYK CMYK is the color space used when color printing. All values of `C`, `M`, `Y`, and `K` must be between `0.0` and `1.0` When using CMYK, you can also specify a tint value between `0.0` and `1.0`. When a CMYK color has a tint value that is not `1.0`, it will be rendered to screen as if transparent over a white background. When rendering to screen, CMYK colors are first converted to RGB using a simple mathematical conversion. Currently, the same conversion happens when exporting a scene to a PDF file. ### Spot Color Spot colors are typically used for special printers or other devices that understand how to use them. Spot colors are defined primarily by their name as that is the information that the device will use to render. For the purpose of rendering a spot color to screen, it must be given either an RGB or CMYK color approximation or both. These approximations adhere to the same restrictions respectively described above. You can specify a tint as a value between `0.0` and `1.0` which will be interpreted as opacity when rendering to screen. It is up to the special printer or device how to interpret the tint value. You can also specify an external reference, a string describing the origin of this spot color. When rendering to screen, the spot color's RGB or CMYK approximation will be used, in that order of preference. When exporting a scene to a PDF file, spot colors will be saved as a [Separation Color Space](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.6.pdf#G9.1850648). Using a spot color is a two step process: 1. you define a spot color with its name and color approximation(s) in the spot color registry. 2. you instantiate a spot color with its name, a tint and an external reference. The spot color registry allows you to: * list the defined spot colors * define a new a spot color with a name and its RGB or CMYK approximation * re-define an existing spot color's RGB or CMYK approximation * retrieve the RGB or CMYK approximation of an already defined spot color * remove a spot color from the list of defined spot colors Multiple blocks and their properties can refer to the same spot color and each can have a different tint and external reference. #### Warning If a block's color property refers to an undefined spot color, the default color magenta with an RGB approximation of (1, 0, 1) will be used. ### Converting between colors A utility function `convertColorToColorSpace` is provided to create a new color from an existing color and a new color space. RGB and CMYK colors can be converted between each other and is done so using a simple mathematical conversion. Spot colors can be converted to RGB and CMYK simply by using the corresponding approximation. RGB and CMYK colors cannot be converted to spot colors. ### Custom Color Libraries You can configure CE.SDK with custom color libraries. More information is found [here](/docs/cesdk/ui/guides/custom-color-libraries/). Explore a full code sample on [Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-colors?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Colors&file=index.js) or view the code on [Github](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-colors). * [Edit on Stackblitz](https://stackblitz.com/fork/github/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-colors?title=IMG.LY%27s+CE.SDK%3A+Engine+Guides+Colors&file=index.js) * [View on GitHub](https://github.com/imgly/cesdk-web-examples/tree/v1.43.0/engine-guides-colors) ## Setup the scene We first create a new scene with a graphic block that has color fill. ``` let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(block, value: 350) try engine.block.setPositionY(block, value: 400) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 100) let fill = try engine.block.createFill(.color) try engine.block.setFill(block, fill: fill) ``` ## Create colors Here we instantiate a few colors with RGB and CMYK color spaces. We also define two spot colors, one with an RGB approximation and another with a CMYK approximation. Note that a spot colors can have both color space approximations. ``` let rgbaBlue = Color.rgba(r: 0, g: 0, b: 1, a: 1) let cmykRed = Color.cmyk(c: 0, m: 1, y: 1, k: 0, tint: 1) let cmykPartialRed = Color.cmyk(c: 0, m: 1, y: 1, k: 0, tint: 0.5) engine.editor.setSpotColor(name: "Pink-Flamingo", r: 0.988, g: 0.455, b: 0.992) engine.editor.setSpotColor(name: "Yellow", c: 0, m: 0, y: 1, k: 0) let spotPinkFlamingo = Color.spot(name: "Pink-Flamingo", tint: 1.0, externalReference: "Crayola") let spotPartialYellow = Color.spot(name: "Yellow", tint: 0.3, externalReference: "") ``` ## Applying colors to a block We can use the defined colors to modify certain properties of a fill or properties of a shape. Here we apply it to `'fill/color/value'`, `'stroke/color'` and `'dropShadow/color'`. ``` try engine.block.setColor(fill, property: "fill/color/value", color: rgbaBlue) try engine.block.setColor(fill, property: "fill/color/value", color: cmykRed) try engine.block.setColor(block, property: "stroke/color", color: cmykPartialRed) try engine.block.setColor(fill, property: "fill/color/value", color: spotPinkFlamingo) try engine.block.setColor(block, property: "dropShadow/color", color: spotPartialYellow) ``` ## Converting colors Using the utility function `convertColorToColorSpace` we create a new color in the CMYK color space by converting the `rgbaBlue` color to the CMYK color space. We also create a new color in the RGB color space by converting the `spotPinkFlamingo` color to the RGB color space. ``` let cmykBlueConverted = try engine.editor.convertColorToColorSpace(color: rgbaBlue, colorSpace: .cmyk) let rgbaPinkFlamingoConverted = try engine.editor.convertColorToColorSpace( color: spotPinkFlamingo, colorSpace: .sRGB ) ``` ## Listing spot colors This function returns the list of currently defined spot colors. ``` engine.editor.findAllSpotColors() // ["Crayola-Pink-Flamingo", "Yellow"] ``` ## Redefine a spot color We can re-define the RGB and CMYK approximations of an already defined spot color. Doing so will change the rendered color of the blocks. We change it for the CMYK approximation of `'Yellow'` and make it a bit greenish. The properties that have `'Yellow'` as their spot color will change when re-rendered. ``` engine.editor.setSpotColor(name: "Yellow", c: 0.2, m: 0, y: 1, k: 0) ``` ## Removing the definition of a spot color We can undefine a spot color. Doing so will make all the properties still referring to that spot color (`'Yellow'` in this case) use the default magenta RGB approximation. ``` try engine.editor.removeSpotColor(name: "Yellow") ``` Creating a Scene from Scratch - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/create-scene-from-scratch/?platform=ios&language=swift # Creating a Scene from Scratch In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk) from scratch and add a star shape. Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-create-scene-from-scratch/CreateSceneFromScratch.swift) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-create-scene-from-scratch/CreateSceneFromScratch.swift) We create an empty scene via `try engine.scene.create()` which sets up the default scene block with a camera attached. Afterwards, the scene can be populated by creating additional blocks and appending them to the scene. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` let scene = try engine.scene.create() ``` We first add a page with `func create(_ type: DesignBlockType) throws -> DesignBlockID` specifying a `.page` and set a parent-child relationship between the scene and this page. ``` let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) ``` To this page, we add a graphic design block, again with `func create(_ type: DesignBlockType) throws -> DesignBlockID`. To make it more interesting, we set a star shape and a color fill to this block to give it a visual representation. Like for the page, we set the parent-child relationship between the page and the newly added block. From then on, modifications to this block are relative to the page. ``` let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.star)) try engine.block.setFill(block, fill: engine.block.createFill(.color)) try engine.block.appendChild(to: page, child: block) ``` This example first appends a page child to the scene as would typically be done but it is not strictly necessary and any child block can be appended directly to a scene. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). Configure the Engine to use Assets served from your own Servers - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/assets-served-from-your-own-servers?language=swift&platform=ios#1-register-imglys-default-assets Platform Web iOS Catalyst macOS Android Language Swift Platform: iOS Language: Swift # Configure the Engine to use Assets served from your own Servers Learn how to serve assets from your own servers in the Engine. In this example, we explain how to configure the Creative Engine to use assets hosted on your own servers. While we serve all assets from our own CDN by default, it is highly recommended to serve the assets from your own servers in a production environment. ## 1\. Register IMG.LY's default assets If you want to use our default asset sources in your integration, call `engine.addDefaultAssetSources(baseURL: URL, exclude: Set)`. Right after initialization: ``` 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. 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/assets/v2/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: ![](/docs/cesdk/f677a42d1489bd83cf7b7e6777310c87/bundle-ios.png) ## 3\. Configure the IMGLYEngine to use your self-hosted assets Next, we need to configure the SDK to use the copied assets instead of the ones served via IMG.LY CDN. `engine.addDefaultAssetSources` offers a `baseURL` option, that needs to be set to an absolute URL, pointing to a valid `Bundle` or a remote location. In case of using your own server, the `baseURL` should point to the root of your asset folder, e.g. `https://cdn.your.custom.domain/assets`: ``` 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: ``` let bundleURL = Bundle.main.url(forResource: "IMGLYAssets", withExtension: "bundle")! Task { try await engine.addDefaultAssetSources(baseURL: bundleURL) } ``` [ Previous Using the Camera ](/docs/cesdk/engine/guides/using-camera/)[ Next Adding Custom Asset Sources ](/docs/cesdk/engine/guides/integrate-a-custom-asset-source/) Load Scenes From a Blob - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/load-scene-from-blob?language=swift&platform=ios#warning # Load Scenes From a Blob In this example, we will show you how to load scenes with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-load-scene-from-blob/LoadSceneFromBlob.swift) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-load-scene-from-blob/LoadSceneFromBlob.swift) Loading an existing scene allows resuming work on a previous session or adapting an existing template to your needs. This can be done by passing a binary string that describes a scene to the `engine.scene.loadFromString()` function. #### Warning Saving a scene can be done as a either _scene file_ or as an _archive file_ (c.f. [Saving scenes](/docs/cesdk/engine/guides/save-scene/)). A _scene file_ does not include any fonts or images. Only the source URIs of assets, the general layout, and element properties are stored. When loading scenes in a new environment, ensure previously used asset URIs are available. Conversely, an _archive file_ contains within it the scene's assets and references them as relative URIs. In this example, we fetch a scene from a remote URL and load it as `sceneBlob`. ``` let sceneURL = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! let sceneBlob = try await URLSession.shared.data(from: sceneURL).0 ``` To acquire a scene string from `sceneBlob`, we need to read its contents into a string. ``` let blobString = String(data: sceneBlob, encoding: .utf8)! ``` We can then pass that string to the `func load(from string: String) async throws -> DesignBlockID` function. The editor will reset and present the given scene to the user. The function is asynchronous and it does not throw if the scene load succeeded. ``` let scene = try await engine.scene.load(from: blobString) ``` From this point on we can continue to modify our scene. In this example, assuming the scene contains a text element, we add a drop shadow to it. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` let text = try engine.block.find(byType: .text).first! try engine.block.setDropShadowEnabled(text, enabled: true) ``` Scene loads may be reverted using `engine.editor.undo()`. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). Load Scenes from a String - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/load-scene-from-string?language=swift&platform=ios#warning # Load Scenes from a String In this example, we will show you how to load scenes from a string with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-load-scene-from-string/LoadSceneFromString.swift) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-load-scene-from-string/LoadSceneFromString.swift) Loading an existing scene allows resuming work on a previous session or adapting an existing template to your needs. This can be done by passing a string that describes a scene to the `engine.scene.loadFromString()` function. #### Warning Saving a scene can be done as a either _scene file_ or as an _archive file_ (c.f. [Saving scenes](/docs/cesdk/engine/guides/save-scene/)). A _scene file_ does not include any fonts or images. Only the source URIs of assets, the general layout, and element properties are stored. When loading scenes in a new environment, ensure previously used asset URIs are available. Conversely, an _archive file_ contains within it the scene's assets and references them as relative URIs. In this example, we fetch a scene from a remote URL and load it as a string. This string could also come from the result of `func saveToString() async throws -> String`. ``` let sceneURL = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! let sceneBlob = try await URLSession.shared.data(from: sceneURL).0 let blobString = String(data: sceneBlob, encoding: .utf8)! ``` We can pass that string to the `func load(from string: String) async throws -> DesignBlockID` function. The editor will then reset and present the given scene to the user. The function is asynchronous and it does not throw if the scene load succeeded. ``` let scene = try await engine.scene.load(from: blobString) ``` From this point on we can continue to modify our scene. In this example, assuming the scene contains a text element, we add a drop shadow to it. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` let text = try engine.block.find(byType: .text).first! try engine.block.setDropShadowEnabled(text, enabled: true) ``` Scene loads may be reverted using `engine.editor.undo()`. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). Selection & Visibility - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-selection-visibility?language=swift&platform=ios#setup # Selection & Visibility In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify scenes through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Select blocks and change their visibility #### Note on Visibility A block can potentially be _invisible_ (in the sense that you can't see it), even though `isVisible()` returns true. This could be the case when a block has not been added to a parent, the parent itself is not visible, or the block is obscured by another block on top of it. ``` public func setSelected(_ id: DesignBlockID, selected: Bool) throws ``` Update the selection state of a block. * Note: Previously selected blocks remain selected. Required scope: "editor/select" * `id`: The block to query. * `selected`: Whether or not the block should be selected. ``` try engine.block.setSelected(block, selected: true) ``` ``` public func isSelected(_ id: DesignBlockID) throws -> Bool ``` Get the selected state of a block. * `id:`: The block to query. * Returns: `true` if the block is selected, `false` otherwise. ``` let isSelected = try engine.block.isSelected(block) ``` ``` public func select(_ id: DesignBlockID) throws ``` Selects the given block and deselects all other blocks. * `id:`: The block to be selected. ``` try engine.block.select(block) ``` ``` public func findAllSelected() -> [DesignBlockID] ``` Get all currently selected blocks. * Returns: An array of block ids. ``` let selectedIds = engine.block.findAllSelected() ``` ``` public var onSelectionChanged: AsyncStream { get } ``` Subscribe to changes in the current set of selected blocks. ``` let selectionTask = Task { for await _ in engine.block.onSelectionChanged { let selectedIDs = engine.block.findAllSelected() print("Selection changed: \(selectedIDs)") } } ``` ``` public var onClicked: AsyncStream { get } ``` Subscribe to block click events. ``` let clickedTask = Task { for await block in engine.block.onClicked { print("Block clicked: \(block)") } } ``` ``` public func setVisible(_ id: DesignBlockID, visible: Bool) throws ``` Update a block's visibility. Required scope: "layer/visibility" * `id`: The block to update. * `visible`: Whether the block shall be visible. ``` try engine.block.setVisible(block, visible: true) ``` ``` public func isVisible(_ id: DesignBlockID) throws -> Bool ``` Query a block's visibility. * `id:`: The block to query. * Returns: `true` if visible, `false` otherwise. ``` let isVisible = try engine.block.isVisible(block) ``` ``` public func setClipped(_ id: DesignBlockID, clipped: Bool) throws ``` Update a block's clipped state. Required scope: "layer/clipping" * `id`: The block to update. * `clipped`: Whether the block should clips its contents to its frame. ``` try engine.block.setClipped(page, clipped: true) ``` ``` public func isClipped(_ id: DesignBlockID) throws -> Bool ``` Query a block's clipped state. If `true`, the block should clip * `id:`: The block to query. * Returns: `True` if clipped, `false` otherwise. ``` let isClipped = try engine.block.isClipped(page) ``` ``` public func isIncludedInExport(_ id: DesignBlockID) throws -> Bool ``` Check if the given block is included on the exported result. * `id:`: The block id to query. * Returns: True if block will be included on the exported result. ``` let isIncludedInExport = try engine.block.isIncludedInExport(block) ``` ``` public func setIncludedInExport(_ id: DesignBlockID, enabled: Bool) throws ``` Set whether you want the given design block to be included in exported result. * `id`: The block to include/exclude from export. * `enabled`: If true, block will be included in the export. ``` try engine.block.setIncludedInExport(block, enabled: true) ``` Change Settings - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/editor-change-settings?language=swift&platform=ios#setup # 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 on the [Settings](/docs/cesdk/engine/api/editor-settings/) page. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Exploration ``` public func findAllSettings() -> [String] ``` Get a list of all available settings. * Returns: A list of all available settings. ``` engine.editor.findAllSettings() ``` ``` 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. ``` try engine.editor.getSettingType("doubleClickSelectionMode") ``` ## Functions ``` public var onSettingsChanged: AsyncStream { get } ``` Subscribe to changes to the editor settings. ``` let settingsTask = Task { for await _ in engine.editor.onSettingsChanged { print("Editor settings have changed") } } ``` ``` public var onRoleChanged: AsyncStream { get } ``` Subscribe to changes to the editor role. ``` let roleTask = Task { for await role in engine.editor.onRoleChanged { print("Role changed to \(role)") } } ``` ``` public func setSettingBool(_ keypath: String, value: Bool) throws ``` Set a boolean setting. * `keypath`: The settings keypath, e.g. `doubleClickToCropEnabled`. * `value`: The value to set. ``` try engine.editor.setSettingBool("doubleClickToCropEnabled", value: true) ``` ``` public func getSettingBool(_ keypath: String) throws -> Bool ``` Get a boolean setting. * `keypath:`: The settings keypath, e.g. `doubleClickToCropEnabled`. * Returns: The current value. ``` try engine.editor.getSettingBool("doubleClickToCropEnabled") ``` ``` public func setSettingInt(_ keypath: String, value: Int) throws ``` Set an integer setting. * `keypath`: The settings keypath. * `value`: The value to set. ``` try engine.editor.setSettingInt("integerSetting", value: 0) ``` ``` public func getSettingInt(_ keypath: String) throws -> Int ``` Get an integer setting. * `keypath:`: The settings keypath. * Returns: The current value. ``` try engine.editor.getSettingInt("integerSetting") ``` ``` public func setSettingFloat(_ keypath: String, value: Float) throws ``` Set a float setting. * `keypath`: The settings keypath, e.g. `positionSnappingThreshold`. * `value`: The value to set. ``` try engine.editor.setSettingFloat("positionSnappingThreshold", value: 2.0) ``` ``` public func getSettingFloat(_ keypath: String) throws -> Float ``` Get a float setting. * `keypath:`: The settings keypath, e.g. `positionSnappingThreshold`. * Returns: The current value. ``` try engine.editor.getSettingFloat("positionSnappingThreshold") ``` ``` public func setSettingString(_ keypath: String, value: String) throws ``` Set a string setting. * `keypath`: The settings keypath, e.g. `license`. * `value`: The value to set. ``` try engine.editor.setSettingString("license", value: "invalid") ``` ``` public func getSettingString(_ keypath: String) throws -> String ``` Get a string setting. * `keypath:`: The settings keypath, e.g. `license`. * Returns: The current value. ``` try engine.editor.getSettingString("license") ``` ``` public func setSettingColor(_ keypath: String, color: Color) throws ``` Set a color setting. * `keypath`: The settings keypath, e.g. `highlightColor`. * `color`: The value to set. ``` try engine.editor.setSettingColor("highlightColor", color: .rgba(r: 1, g: 0, b: 1, a: 1)) // Pink ``` ``` 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. ``` try engine.editor.getSettingColor("highlightColor") as Color ``` ``` 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. ``` try engine.editor.setSettingEnum("doubleClickSelectionMode", value: "Direct") ``` ``` public func getSettingEnum(_ keypath: String) throws -> String ``` Get an enum setting. * `keypath:`: The settings keypath, e.g. `doubleClickSelectionMode`. * Returns: The value as string. ``` try engine.editor.getSettingEnum("doubleClickSelectionMode") ``` ``` 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. ``` try engine.editor.getSettingEnumOptions("doubleClickSelectionMode") ``` ``` public func setSettingFloat(_ keypath: String, value: Float) throws ``` Set a float setting. * `keypath`: The settings keypath, e.g. `positionSnappingThreshold`. * `value`: The value to set. ``` try engine.editor.setSettingFloat("positionSnappingThreshold", value: 2.0) ``` ``` public func getSettingFloat(_ keypath: String) throws -> Float ``` Get a float setting. * `keypath:`: The settings keypath, e.g. `positionSnappingThreshold`. * Returns: The current value. ``` try engine.editor.getSettingFloat("positionSnappingThreshold") ``` ``` public func setSettingString(_ keypath: String, value: String) throws ``` Set a string setting. * `keypath`: The settings keypath, e.g. `license`. * `value`: The value to set. ``` try engine.editor.setSettingString("license", value: "invalid") ``` ``` public func getSettingString(_ keypath: String) throws -> String ``` Get a string setting. * `keypath:`: The settings keypath, e.g. `license`. * Returns: The current value. ``` try engine.editor.getSettingString("license") ``` ``` public func getRole() throws -> String ``` Get the current role of the user. * Returns: The current role of the user. ``` try engine.editor.getRole() ``` ``` 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. ``` try engine.editor.setRole("Adopter") ``` Integrate the Mobile Editor - CE.SDK | IMG.LY Docs [ios/uikit/swift] https://img.ly/docs/cesdk/mobile-editor/quickstart?platform=ios&language=swift&framework=uikit # Integrate the Mobile Editor In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s mobile editor in your iOS app. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-quickstart/). * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-quickstart/) ### Requirements The mobile editor requires iOS 16 and Swift 5.10 (Xcode 15.4) or later. ### Using Swift Package Manager If you use [Swift Package Manager](https://github.com/apple/swift-package-manager) to build your app and want to integrate the Creative Engine and UI modules using your regular workflows, add the [IMGLYUI Swift Package](https://github.com/imgly/IMGLYUI-swift) as a dependency to your project. ![](/docs/cesdk/3c5227dab88fd8e5a0ca5fb8deeb16e1/spm-ui.png) This package provides multiple library products. Add the default `IMGLYUI` library to your app target to add all available UI modules included in this package to your app. To keep your app size minimal, only add the library product that you need, e.g., only add the `IMGLYDesignEditor` library if you need to `import IMGLYDesignEditor` in your code. On the _General_ page of your app target's Xcode project settings the _Frameworks, Libraries, and Embedded Content_ section lists all used library products. ## Usage In this example, the basic usage of the mobile editor is going to be demonstrated using the [Design Editor](/docs/cesdk/mobile-editor/solutions/design-editor/) solution, however, it is exactly the same for all the other [solutions](/docs/cesdk/mobile-editor/solutions/). ### Import You can get started right away by importing the editor module into your own code. ``` import IMGLYDesignEditor ``` ### Initialization Each editor is initialized with `EngineSettings`. You need to provide the license key that you received from IMG.LY. Optionally, you can provide a unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU) and it is especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. ``` DesignEditor(.init(license: secrets.licenseKey, userID: "")) ``` ### Environment Each editor view needs to be embedded in a navigation environment as it manages the toolbar used by the editor. In this example, the `ModalEditor` helper provides the navigation environment. Alternatively, the editor could also be used as the destination of a `NavigationLink`. ``` ModalEditor { DesignEditor(.init(license: secrets.licenseKey, userID: "")) } ``` The `ModalEditor` helper injects the dismiss button for the editor. If the `ModalEditor` helper is not used and the editor is setup as the destination of a `NavigationLink` the back button is provided by the navigation hierarchy. ``` NavigationView { editor() .onPreferenceChange(BackButtonHiddenKey.self) { newValue in isBackButtonHidden = newValue } .toolbar { ToolbarItem(placement: .navigationBarLeading) { if !isBackButtonHidden { dismissButton } } } } .navigationViewStyle(.stack) ``` Since the editor is entirely written in SwiftUI it needs to be integrated with a `UIHostingController` object into a UIKit view hierarchy. ``` private lazy var editor = UIHostingController(rootView: ModalEditor { DesignEditor(.init(license: secrets.licenseKey, userID: "")) } ) ``` In this integration example the editor is presented as a modal view after tapping a button. ``` private lazy var button = UIButton( type: .system, primaryAction: UIAction(title: "Use the Editor") { [unowned self] _ in editor.modalPresentationStyle = .fullScreen present(editor, animated: true) } ) ``` That is all. Check all the available [solutions](/docs/cesdk/mobile-editor/solutions/) in order to decide which solution fits you best. For more than basic configuration, check out all the available [configurations](/docs/cesdk/mobile-editor/configuration/). Groups - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-groups?language=swift&platform=ios#setup # Groups In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to group blocks through the `block` API. Groups form a cohesive unit. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Grouping Multiple blocks can be grouped together to form a cohesive unit. A group being a block, it can itself be part of a group. #### What cannot be grouped * A scene * A block that already is part of a group ``` public func isGroupable(_ ids: [DesignBlockID]) throws -> Bool ``` Confirms that a given set of blocks can be grouped together. * `ids:`: A non-empty array of block ids. * Returns: Whether the blocks can be grouped together. ``` if try engine.block.isGroupable([member1, member2]) { ``` ``` public func group(_ ids: [DesignBlockID]) throws -> DesignBlockID ``` Group blocks together. * `ids:`: A non-empty array of block ids. * Returns: The block id of the created group. ``` let group = try engine.block.group([member1, member2]) ``` ``` public func ungroup(_ id: DesignBlockID) throws ``` Ungroups a group. * `id:`: The group id from a previous call to `group`. ``` try engine.block.ungroup(group) ``` ``` public func enterGroup(_ id: DesignBlockID) throws ``` Changes selection from selected group to a block within that group. Nothing happens if `id` is not a group. Required scope: "editor/select" * `id:`: The group id from a previous call to `group`. ``` try engine.block.enterGroup(group) ``` ``` public func exitGroup(_ id: DesignBlockID) throws ``` Changes selection from a group's selected block to that group. Nothing happens if the `id` is not part of a group. Required scope: "editor/select" * `id:`: A block id. ``` try engine.block.exitGroup(member1) ``` Creating a Scene From an Initial Image URL - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/create-scene-from-image-url/?platform=ios&language=swift # Creating a Scene From an Initial Image URL In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk) with an initial image. Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-create-scene-from-image-url/CreateSceneFromImageURL.swift) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-create-scene-from-image-url/CreateSceneFromImageURL.swift) Starting from an existing image allows you to use the editor for customizing individual assets. This is done by using `func create(fromImage url: URL, dpi: Float = 300, pixelScaleFactor: Float = 1) async throws -> DesignBlockID` and passing a URL as argument. The `dpi` argument sets the dots per inch of the scene. The `pixelScaleFactor` sets the display's pixel scale factor. Specify the source to use for the initial image. This can be a relative path or a remote URL. ``` let scene = try await engine.scene.create(fromImage: URL(string: "https://img.ly/static/ubq_samples/sample_4.jpg")!) ``` We can retrieve the graphic block id of this initial image using `func find(byType type: DesignBlockType) throws -> [DesignBlockID]`. Note that that function returns an array. Since there's only a single graphic block in the scene, the block is at index `0`. ``` // Find the automatically added graphic block in the scene that contains the image fill. let block = try engine.block.find(byType: .graphic).first! ``` We can then manipulate and modify this block. Here we modify its opacity with `func setOpacity(_ id: DesignBlockID, value: Float) throws`. See [Modifying Scenes](/docs/cesdk/engine/guides/blocks/) for more details. ``` // Change its opacity. try engine.block.setOpacity(block, value: 0.5) ``` When starting with an initial image, the scene's page dimensions match the given resource and the scene is configured to be in pixel design units. To later save your scene, see [Saving Scenes](/docs/cesdk/engine/guides/save-scene/). Spot Colors - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/editor-spot-colors?language=swift&platform=ios#setup # Spot Colors In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/creative-sdk)'s CreativeEngine to manage spot colors in the `editor` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` public func findAllSpotColors() -> [String] ``` Queries the names of currently set spot colors previously set with `setSpotColor`. * Returns: The names of set spot colors. ``` engine.editor.findAllSpotColors() // ["Red", "Yellow"] ``` ``` public func getSpotColor(name: String) -> RGBA ``` Queries the RGB representation set for a spot color. If the value of the queried spot color has not been set yet, returns the default RGB representation (of magenta). The alpha value is always 1.0. * `name:`: The name of a spot color. * Returns: A result holding the four color components. ``` let rgbaSpotRed: RGBA = engine.editor.getSpotColor(name: "Red") ``` ``` public func getSpotColor(name: String) -> CMYK ``` Queries the CMYK representation set for a spot color. If the value of the queried spot color has not been set yet, returns the default CMYK representation (of magenta). * `name:`: The name of a spot color. * Returns: A result holding the four color components. ``` let cmykSpotRed: CMYK = engine.editor.getSpotColor(name: "Red") ``` ``` public func setSpotColor(name: String, r: Float, g: Float, b: Float) ``` Sets the RGB representation of a spot color. Use this function to both create a new spot color or update an existing spot color. * `name`: The name of a spot color. * `r`: The red color component in the range of 0 to 1. * `g`: The green color component in the range of 0 to 1. * `b`: The blue color component in the range of 0 to 1. ``` engine.editor.setSpotColor(name: "Red", r: 1.0, g: 0.0, b: 0.0) ``` ``` public func setSpotColor(name: String, c: Float, m: Float, y: Float, k: Float) ``` Sets the CMYK representation of a spot color. Use this function to both create a new spot color or update an existing spot color. * `name`: The name of a spot color. * `c`: The cyan color component in the range of 0 to 1. * `m`: The magenta color component in the range of 0 to 1. * `y`: The yellow color component in the range of 0 to 1. * `k`: The key color component in the range of 0 to 1. ``` engine.editor.setSpotColor(name: "Yellow", c: 0.0, m: 0.0, y: 1.0, k: 0.0) ``` ``` public func removeSpotColor(name: String) throws ``` Removes a spot color from the list of set spot colors. * `name:`: The name of a spot color. ``` try engine.editor.removeSpotColor(name: "Red") ``` History - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/editor-history?language=swift&platform=ios#setup # History In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to undo and redo steps in the `editor` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` public func createHistory() -> History ``` Create a history which consists of an undo/redo stack for editing operations. There can be multiple. But only one can be active at a time. * Returns: The handle to the created history. ``` let newHistory = engine.editor.createHistory() ``` ``` public func destroyHistory(_ history: History) ``` Destroy the given history, returns an error if the handle doesn't refer to a history. * `history:`: The history to be destroyed. ``` engine.editor.destroyHistory(oldHistory) ``` ``` public func setActiveHistory(_ history: History) ``` Mark the given history as active, returns an error if the handle doesn't refer to a history. All other histories get cleared from the active state. Undo/redo operations only apply to the active history. * `history:`: The history to be marked as active. ``` engine.editor.setActiveHistory(newHistory) ``` ``` public func getActiveHistory() -> History ``` Get the handle to the currently active history. If there's none it will be created. * Returns: The handle to the active history. ``` let oldHistory = engine.editor.getActiveHistory() ``` ``` public func addUndoStep() throws ``` Adds a new history state to the stack, if undoable changes were made. ``` try engine.editor.addUndoStep() ``` ``` public func undo() throws ``` Undo one step in the history if an undo step is available. ``` try engine.editor.undo() ``` ``` public func canUndo() throws -> Bool ``` If an undo step is available. * Returns: `true` if an undo step is available. ``` if try engine.editor.canUndo() { ``` ``` public func redo() throws ``` Redo one step in the history if a redo step is available. ``` try engine.editor.redo() ``` ``` public func canRedo() throws -> Bool ``` If a redo step is available. * Returns: `true` if a redo step is available. ``` if try engine.editor.canRedo() { ``` ``` public var onHistoryUpdated: AsyncStream { get } ``` Subscribe to changes to the undo/redo history. ``` let historyTask = Task { for await _ in engine.editor.onHistoryUpdated { let canUndo = try engine.editor.canUndo() let canRedo = try engine.editor.canRedo() print("History updated: \(canUndo) \(canRedo)") } } ``` Configure Asset Library - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/mobile-editor/configuration/asset-library/?platform=ios&language=swift # Configure Asset Library In this example, we will show you how to customize the asset library for the mobile editor. The example is based on the `Design Editor`, however, it is exactly the same for all the other [solutions](/docs/cesdk/mobile-editor/solutions/). Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-configuration-asset-library/). * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-configuration-asset-library/) ## Modifiers After initializing an editor SwiftUI view you can apply any SwiftUI _modifier_ to customize it like for any other SwiftUI view. All public Swift `extension`s of existing types provided by IMG.LY, e.g., for the SwiftUI `View` protocol, are exposed in a separate `.imgly` property namespace. The asset library configuration to customize the editor is no exception to this rule and is implemented as a SwiftUI _modifier_. ``` DesignEditor(settings) ``` * `assetLibrary` - the asset library UI definition used by the editor. The result of the trailing closure needs to conform to the `AssetLibrary` protocol. By default, the predefined `DefaultAssetLibrary` is used. ``` .imgly.assetLibrary { DefaultAssetLibrary( tabs: DefaultAssetLibrary.Tab.allCases.reversed().filter { tab in tab != .elements && tab != .uploads } ) .images { AssetLibrarySource.image(.title("Unsplash"), source: .init(id: UnsplashAssetSource.id)) DefaultAssetLibrary.images } } ``` ### Custom Asset Source To use custom asset sources in the asset library UI, the custom asset source must be first added to the engine. In addition to creating or loading a scene, registering the asset sources should be done in the [`onCreate` callback](/docs/cesdk/mobile-editor/configuration/callbacks/). In this example, the `OnCreate.loadScene` default implementation is used and afterward, the [custom `UnsplashAssetSource`](/docs/cesdk/engine/guides/integrate-a-custom-asset-source/) is added. ``` .imgly.onCreate { engine in try await OnCreate.loadScene(from: DesignEditor.defaultScene)(engine) try engine.asset.addSource(UnsplashAssetSource(host: secrets.unsplashHost)) } ``` ### Default Asset Library The `DefaultAssetLibrary` is a predefined `AssetLibrary` intended to quickly customize some parts of the default asset library without implementing a complete `AssetLibrary` from scratch. It can be initialized with a custom selection and ordering of the available tabs. In this example, we reverse the ordering and exclude the elements and uploads tab. ``` DefaultAssetLibrary( tabs: DefaultAssetLibrary.Tab.allCases.reversed().filter { tab in tab != .elements && tab != .uploads } ) ``` ### Asset Library Builder The content of some of the tabs can be changed with _modifiers_ that are defined on the `DefaultAssetLibrary` type and expect a trailing `@AssetLibraryBuilder` closure similar to regular SwiftUI `@ViewBuilder` closures. These type-bound _modifiers_ are `videos`, `audio`, `images`, `shapes`, and `stickers`. The elements tab will then be generated with these definitions. In this example, we reuse the `DefaultAssetLibrary.images` default implementation and add a new `AssetLibrarySource` for the [previously added `UnsplashAssetSource`](/docs/cesdk/mobile-editor/configuration/asset-library/#custom-asset-source) which will add a new "Unsplash" section to the asset library UI. ``` .images { AssetLibrarySource.image(.title("Unsplash"), source: .init(id: UnsplashAssetSource.id)) DefaultAssetLibrary.images } ``` ### Custom Asset Library If the `DefaultAssetLibrary` is not customizable enough for your use case you can create your own custom `AssetLibrary`. ``` .imgly.assetLibrary { CustomAssetLibrary() } ``` In this example, we did exactly that by creating the `CustomAssetLibrary`. It resembles the above customized `DefaultAssetLibrary` with the added `UnsplashAssetSource` but without the custom tab configuration which is not needed as you control every section, layout, and grouping. ``` import IMGLYEditor import SwiftUI @MainActor struct CustomAssetLibrary: AssetLibrary { @Environment(\.imglyAssetLibrarySceneMode) var sceneMode ``` As used above for customizing the `DefaultAssetLibrary` with its _modifiers_, the `@AssetLibraryBuilder` concept is the foundation to quickly create any asset library hierarchy. It behaves and feels like the regular SwiftUI `@ViewBuilder` syntax. You compose your asset library out of `AssetLibrarySource`s that can be organized in named `AssetLibraryGroup`s. There are different flavors of these two for each asset type which define the used asset preview and section styling. ``` @AssetLibraryBuilder var uploads: AssetLibraryContent { AssetLibrarySource.imageUpload(.title("Images"), source: .init(demoSource: .imageUpload)) if sceneMode == .video { AssetLibrarySource.videoUpload(.title("Videos"), source: .init(demoSource: .videoUpload)) } } @AssetLibraryBuilder var videosAndImages: AssetLibraryContent { AssetLibraryGroup.video("Videos") { videos } AssetLibraryGroup.image("Images") { images } AssetLibraryGroup.upload("Photo Roll") { AssetLibrarySource.imageUpload(.title("Images"), source: .init(demoSource: .imageUpload)) AssetLibrarySource.videoUpload(.title("Videos"), source: .init(demoSource: .videoUpload)) } } @AssetLibraryBuilder var videos: AssetLibraryContent { AssetLibrarySource.video(.title("Videos"), source: .init(demoSource: .video)) AssetLibrarySource.videoUpload(.title("Photo Roll"), source: .init(demoSource: .videoUpload)) } @AssetLibraryBuilder var audio: AssetLibraryContent { AssetLibrarySource.audio(.title("Audio"), source: .init(demoSource: .audio)) AssetLibrarySource.audioUpload(.title("Uploads"), source: .init(demoSource: .audioUpload)) } @AssetLibraryBuilder var images: AssetLibraryContent { AssetLibrarySource.image(.title("Unsplash"), source: .init(id: UnsplashAssetSource.id)) AssetLibrarySource.image(.title("Images"), source: .init(demoSource: .image)) AssetLibrarySource.imageUpload(.title("Photo Roll"), source: .init(demoSource: .imageUpload)) } let text = AssetLibrarySource.text(.title("Text"), source: .init(id: TextAssetSource.id)) @AssetLibraryBuilder var shapes: AssetLibraryContent { AssetLibrarySource.shape(.title("Basic"), source: .init( defaultSource: .vectorPath, config: .init(groups: ["//ly.img.cesdk.vectorpaths/category/vectorpaths"]))) AssetLibrarySource.shape(.title("Abstract"), source: .init( defaultSource: .vectorPath, config: .init(groups: ["//ly.img.cesdk.vectorpaths.abstract/category/abstract"]))) } @AssetLibraryBuilder var stickers: AssetLibraryContent { AssetLibrarySource.sticker(.titleForGroup { group in if let name = group?.split(separator: "/").last { name.capitalized } else { "Stickers" } }, source: .init(defaultSource: .sticker)) } @AssetLibraryBuilder var elements: AssetLibraryContent { AssetLibraryGroup.upload("Photo Roll") { uploads } if sceneMode == .video { AssetLibraryGroup.video("Videos") { videos } AssetLibraryGroup.audio("Audio") { audio } } AssetLibraryGroup.image("Images") { images } text AssetLibraryGroup.shape("Shapes") { shapes } AssetLibraryGroup.sticker("Stickers") { stickers } } ``` To compose a SwiftUI view for any `AssetLibraryBuilder` result you use an `AssetLibraryTab` which can be added to your view hierarchy. In this example, we reuse the labels defined in the `DefaultAssetLibrary` but you can also use your own SwiftUI `Label` or any other view. The argument of the `label` closure just forwards the title that was used to initialize the `AssetLibraryTab` so that you don't have to type it twice. ``` @ViewBuilder var uploadsTab: some View { AssetLibraryTab("Photo Roll") { uploads } label: { DefaultAssetLibrary.uploadsLabel($0) } } @ViewBuilder var elementsTab: some View { AssetLibraryTab("Elements") { elements } label: { DefaultAssetLibrary.elementsLabel($0) } } @ViewBuilder var videosTab: some View { AssetLibraryTab("Videos") { videos } label: { DefaultAssetLibrary.videosLabel($0) } } @ViewBuilder var audioTab: some View { AssetLibraryTab("Audio") { audio } label: { DefaultAssetLibrary.audioLabel($0) } } @ViewBuilder var imagesTab: some View { AssetLibraryTab("Images") { images } label: { DefaultAssetLibrary.imagesLabel($0) } } @ViewBuilder var textTab: some View { AssetLibraryTabView("Text") { text.content } label: { DefaultAssetLibrary.textLabel($0) } } @ViewBuilder var shapesTab: some View { AssetLibraryTab("Shapes") { shapes } label: { DefaultAssetLibrary.shapesLabel($0) } } @ViewBuilder var stickersTab: some View { AssetLibraryTab("Stickers") { stickers } label: { DefaultAssetLibrary.stickersLabel($0) } } @ViewBuilder public var clipsTab: some View { AssetLibraryTab("Clips") { videosAndImages } label: { _ in EmptyView() } } @ViewBuilder public var overlaysTab: some View { AssetLibraryTab("Overlays") { videosAndImages } label: { _ in EmptyView() } } @ViewBuilder public var stickersAndShapesTab: some View { AssetLibraryTab("Stickers") { stickers shapes } label: { _ in EmptyView() } } ``` ### Asset Library `body` View Finally, multiple `AssetLibraryTab`s are ready to be used in a SwiftUI `TabView` environment. Use an `AssetLibraryMoreTab` if you have more than five tabs to workaround various SwiftUI `TabView` shortcomings. Editor solutions with a floating "+" action button (FAB) show this `AssetLibrary.body` `View`. ``` var body: some View { TabView { if sceneMode == .video { elementsTab uploadsTab videosTab audioTab AssetLibraryMoreTab { imagesTab textTab shapesTab stickersTab } } else { elementsTab imagesTab textTab shapesTab stickersTab } } } ``` ### Asset Library Tab Views In addition to its `View.body`, the `AssetLibrary` protocol requires to define `elementsTab`, `videosTab`, `audioTab`, `imagesTab`, `textTab`, `shapesTab`, and `stickersTab` `View`s. These are used when displaying isolated asset libraries just for the corresponding asset type, e.g., for replacing an asset. ``` @ViewBuilder var elementsTab: some View { AssetLibraryTab("Elements") { elements } label: { DefaultAssetLibrary.elementsLabel($0) } } @ViewBuilder var videosTab: some View { AssetLibraryTab("Videos") { videos } label: { DefaultAssetLibrary.videosLabel($0) } } @ViewBuilder var audioTab: some View { AssetLibraryTab("Audio") { audio } label: { DefaultAssetLibrary.audioLabel($0) } } @ViewBuilder var imagesTab: some View { AssetLibraryTab("Images") { images } label: { DefaultAssetLibrary.imagesLabel($0) } } @ViewBuilder var textTab: some View { AssetLibraryTabView("Text") { text.content } label: { DefaultAssetLibrary.textLabel($0) } } @ViewBuilder var shapesTab: some View { AssetLibraryTab("Shapes") { shapes } label: { DefaultAssetLibrary.shapesLabel($0) } } @ViewBuilder var stickersTab: some View { AssetLibraryTab("Stickers") { stickers } label: { DefaultAssetLibrary.stickersLabel($0) } } ``` For the [video editor solution](/docs/cesdk/mobile-editor/solutions/video-editor/), it is also required to define `clipsTab`, `overlaysTab`, and `stickersAndShapesTab` `View`s. These composed libraries are used as entry points instead of the FAB. ``` @ViewBuilder public var clipsTab: some View { AssetLibraryTab("Clips") { videosAndImages } label: { _ in EmptyView() } } @ViewBuilder public var overlaysTab: some View { AssetLibraryTab("Overlays") { videosAndImages } label: { _ in EmptyView() } } @ViewBuilder public var stickersAndShapesTab: some View { AssetLibraryTab("Stickers") { stickers shapes } label: { _ in EmptyView() } } ``` Modify Properties - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-properties?language=swift&platform=ios#setup # Modify Properties In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify block properties through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## UUID A universally unique identifier (UUID) is assigned to each block upon creation and can be queried. This is stable across save & load and may be used to reference blocks. ``` public func getUUID(_ id: DesignBlockID) throws -> String ``` Get a block's unique identifier. * `id:`: The block to query. * Returns: The block's UUID. ``` let uuid = try engine.block.getUUID(block) ``` ## Reflection For every block, you can get a list of all its properties by calling `findAllProperties(id: number): string[]`. Properties specific to a block are prefixed with the block's type followed by a forward slash. There are also common properties shared between blocks which are prefixed by their respective type. A list of all properties can be found in the [Blocks Overview](/docs/cesdk/engine/api/block/). ``` let propertyNamesStar = try engine.block .findAllProperties(starShape) // Array [ "shape/star/innerDiameter", "shape/star/points", "opacity/value", ... ] let propertyNamesImage = try engine.block .findAllProperties(imageFill) // Array [ "fill/image/imageFileURI", "fill/image/previewFileURI", "fill/image/externalReference", ... ] let propertyNamesText = try engine.block .findAllProperties(text) // Array [ "text/text", "text/fontFileUri", "text/externalReference", "text/fontSize", "text/horizontalAlignment", ... ] ``` ``` public func findAllProperties(_ id: DesignBlockID) throws -> [String] ``` Get all available properties of a block. * `id:`: The block whose properties should be queried. * Returns: A list of the property names. ``` let propertyNamesStar = try engine.block .findAllProperties(starShape) // Array [ "shape/star/innerDiameter", "shape/star/points", "opacity/value", ... ] let propertyNamesImage = try engine.block .findAllProperties(imageFill) // Array [ "fill/image/imageFileURI", "fill/image/previewFileURI", "fill/image/externalReference", ... ] let propertyNamesText = try engine.block .findAllProperties(text) // Array [ "text/text", "text/fontFileUri", "text/externalReference", "text/fontSize", "text/horizontalAlignment", ... ] ``` Given a property, you can query its type using `getType(ofProperty:)`. ``` let pointsType = try engine.block.getType(ofProperty: "shape/star/points") // "Int" ``` ``` public func getType(ofProperty property: String) throws -> PropertyType ``` Get the type of a property given its name. * `property:`: The name of the property whose type should be queried. * Returns: The property type. ``` let pointsType = try engine.block.getType(ofProperty: "shape/star/points") // "Int" ``` The property type `'Enum'` is a special type. Properties of this type only accept a set of certain strings. To get a list of possible values for an enum property call `getEnumValues(enumProperty: string): string[]`. ``` let alignmentType = try engine.block.getType(ofProperty: "text/horizontalAlignment") // "Enum" try engine.block.getEnumValues(ofProperty: "text/horizontalAlignment") ``` ``` public func getEnumValues(ofProperty enumProperty: String) throws -> [String] ``` Get all the possible values of an enum given an enum property. * `enumProperty:`: The name of the property whose enum values should be queried. * Returns: A list of the enum value names as string. ``` let alignmentType = try engine.block.getType(ofProperty: "text/horizontalAlignment") // "Enum" try engine.block.getEnumValues(ofProperty: "text/horizontalAlignment") ``` Some properties can only be written to or only be read. To find out what is possible with a property, you can use the `isPropertyReadable` and `isPropertyWritable` methods. ``` let readable = try engine.block.isPropertyReadable(property: "shape/star/points") let writable = try engine.block.isPropertyWritable(property: "shape/star/points") ``` ``` public func isPropertyReadable(property: String) throws -> Bool ``` Check if a property with a given name is readable. * `property:`: The name of the property whose type should be queried. * Returns: Whether the property is readable or not. Will return false for unknown properties. ``` let readable = try engine.block.isPropertyReadable(property: "shape/star/points") ``` ``` public func isPropertyWritable(property: String) throws -> Bool ``` Check if a property with a given name is writable. * `property:`: The name of the property whose type should be queried. * Returns: Whether the property is writable or not. Will return false for unknown properties. ``` let writable = try engine.block.isPropertyWritable(property: "shape/star/points") ``` ## Generic Properties There are dedicated setter and getter functions for each property type. You have to provide a block and the property path. Use `findAllProperties` to get a list of all the available properties a block has. Please make sure you call the setter and getter function matching the type of the property you want to set or query or else you will get an error. Use `getType` to figure out the pair of functions you need to use. ``` public func findAllProperties(_ id: DesignBlockID) throws -> [String] ``` Get all available properties of a block. * `id:`: The block whose properties should be queried. * Returns: A list of the property names. ``` let propertyNamesStar = try engine.block .findAllProperties(starShape) // Array [ "shape/star/innerDiameter", "shape/star/points", "opacity/value", ... ] let propertyNamesImage = try engine.block .findAllProperties(imageFill) // Array [ "fill/image/imageFileURI", "fill/image/previewFileURI", "fill/image/externalReference", ... ] let propertyNamesText = try engine.block .findAllProperties(text) // Array [ "text/text", "text/fontFileUri", "text/externalReference", "text/fontSize", "text/horizontalAlignment", ... ] ``` ``` public func setBool(_ id: DesignBlockID, property: String, value: Bool) throws ``` Set a bool property of the given design block to the given value. * `id`: The block whose property should be set. * `property`: The name of the property to set. * `value`: The value to set. ``` try engine.block.setBool(scene, property: "scene/aspectRatioLock", value: false) ``` ``` public func getBool(_ id: DesignBlockID, property: String) throws -> Bool ``` Get the value of a bool property of the given design block. * `id`: The block whose property should be queried. * `property`: The name of the property to query. * Returns: The value of the property. ``` try engine.block.getBool(scene, property: "scene/aspectRatioLock") ``` ``` public func setInt(_ id: DesignBlockID, property: String, value: Int) throws ``` Set an int property of the given design block to the given value. * `id`: The block whose property should be set. * `property`: The name of the property to set. * `value`: The value to set. ``` try engine.block.setInt(starShape, property: "shape/star/points", value: points + 2) ``` ``` public func getInt(_ id: DesignBlockID, property: String) throws -> Int ``` Get the value of an int property of the given design block. * `id`: The block whose property should be queried. * `property`: The name of the property to query. * Returns: The value of the property. ``` let points = try engine.block.getInt(starShape, property: "shape/star/points") ``` ``` public func setFloat(_ id: DesignBlockID, property: String, value: Float) throws ``` Set a float property of the given design block to the given value. * `id`: The block whose property should be set. * `property`: The name of the property to set. * `value`: The value to set. ``` try engine.block.setFloat(starShape, property: "shape/star/innerDiameter", value: 0.75) ``` ``` public func getFloat(_ id: DesignBlockID, property: String) throws -> Float ``` Get the value of a float property of the given design block. * `id`: The block whose property should be queried. * `property`: The name of the property to query. * Returns: The value of the property. ``` try engine.block.getFloat(starShape, property: "shape/star/innerDiameter") ``` ``` public func setDouble(_ id: DesignBlockID, property: String, value: Double) throws ``` Set a double property of the given design block to the given value. * `id`: The block whose property should be set. * `property`: The name of the property to set. * `value`: The value to set. ``` let audio = try engine.block.create(.audio) try engine.block.appendChild(to: scene, child: audio) try engine.block.setDouble(audio, property: "playback/duration", value: 1.0) ``` ``` public func getDouble(_ id: DesignBlockID, property: String) throws -> Double ``` Get the value of a double property of the given design block. * `id`: The block whose property should be queried. * `property`: The name of the property to query. * Returns: The value of the property. ``` try engine.block.getDouble(audio, property: "playback/duration") ``` ``` public func setString(_ id: DesignBlockID, property: String, value: String) throws ``` Set a string property of the given design block to the given value. * `id`: The block whose property should be set. * `property`: The name of the property to set. * `value`: The value to set. ``` try engine.block.setString(text, property: "text/text", value: "*o*") ``` ``` public func getString(_ id: DesignBlockID, property: String) throws -> String ``` Get the value of a string property of the given design block. * `id`: The block whose property should be queried. * `property`: The name of the property to query. * Returns: The value of the property. ``` try engine.block.getString(text, property: "text/text") ``` ``` public func setURL(_ id: DesignBlockID, property: String, value: URL) throws ``` Set a URL property of the given design block to the given value. * `block`: The block whose property should be set. * `property`: The name of the property to set. * `value`: The value to set. ``` try engine.block.setURL( imageFill, property: "fill/image/imageFileURI", value: URL(string: "https://img.ly/static/ubq_samples/sample_4.jpg")! ) ``` ``` public func getURL(_ id: DesignBlockID, property: String) throws -> URL ``` Get the value of a URL property of the given design block. * `block`: The block whose property should be queried. * `property`: The name of the property to set. * Returns: The value of the property. ``` try engine.block.getURL(imageFill, property: "fill/image/imageFileURI") ``` ``` public func setColor(_ id: DesignBlockID, property: String, color: Color) throws ``` Set a color property of the given design block to the given value. * `id`: The block whose property should be set. * `property`: The name of the property to set. * `color`: The value to set. ``` try engine.block.setColor(colorFill, property: "fill/color/value", color: .rgba(r: 1, g: 1, b: 1, a: 1)) // White ``` ``` public func getColor(_ id: DesignBlockID, property: String) throws -> Color ``` Get the value of a color property of the given design block. * `id`: The block whose property should be queried. * `property`: The name of the property to query. * Returns: The color value of the property. ``` try engine.block.getColor(colorFill, property: "fill/color/value") as Color ``` ``` public func setEnum(_ id: DesignBlockID, property: String, value: String) throws ``` Set an enum property of the given design block to the given value. * `id`: The block whose property should be set. * `property`: The name of the property to set. * `value`: The enum value as string. ``` try engine.block.setEnum(text, property: "text/horizontalAlignment", value: "Center") try engine.block.setEnum(text, property: "text/verticalAlignment", value: "Center") ``` ``` public func getEnum(_ id: DesignBlockID, property: String) throws -> String ``` Get the value of an enum property of the given design block. * `id`: The block whose property should be queried. * `property`: The name of the property to query. * Returns: The value as string. ``` try engine.block.getEnum(text, property: "text/horizontalAlignment") try engine.block.getEnum(text, property: "text/verticalAlignment") ``` ``` public func setGradientColorStops(_ id: DesignBlockID, property: String, colors: [GradientColorStop]) throws ``` Set a gradient color stops property of the given design block. * `id`: The block whose property should be set. * `property`: The name of the property to set. * `colors`: The colors to set. ``` try engine.block.setGradientColorStops(gradientFill, property: "fill/gradient/colors", colors: [ .init(color: .rgba(r: 1.0, g: 0.8, b: 0.2, a: 1.0), stop: 0), .init(color: .rgba(r: 0.3, g: 0.4, b: 0.7, a: 1.0), stop: 1) ]) ``` ``` public func getGradientColorStops(_ id: DesignBlockID, property: String) throws -> [GradientColorStop] ``` Get the gradient color stops property of the given design block. * `id`: The block whose property should be queried. * `property`: The name of the property to query. * Returns: The gradient colors. ``` try engine.block.getGradientColorStops(gradientFill, property: "fill/gradient/colors") ``` ``` public func setSourceSet(_ id: DesignBlockID, property: String, sourceSet: [Source]) throws ``` Set the source set of the given block. * `id`: The block whose source set should be set. * `sourceSet`: The new source set. ``` let imageFill = try engine.block.createFill(.image) try engine.block.setSourceSet(imageFill, property: "fill/image/sourceSet", sourceSet: [ .init( uri: URL(string: "http://img.ly/my-image.png")!, width: 800, height: 600 ) ]) ``` ``` public func getSourceSet(_ id: DesignBlockID, property: String) throws -> [Source] ``` Returns the source set of the given block. * `id:`: The block whose source set should be returned. * Returns: The source set of the given block. ``` _ = try engine.block.getSourceSet(imageFill, property: "fill/image/sourceSet") ``` ``` public func addImageFileURIToSourceSet(_ id: DesignBlockID, property: String, uri: URL) async throws ``` Add a source to the `sourceSet` property of the given block. * `id`: The block whose source set should be set. * `property`: The name of the property to modify. * `uri`: The source to add to the source set. ``` try await engine.block.addImageFileURIToSourceSet(imageFill, property: "fill/image/sourceSet", uri: "https://img.ly/static/ubq_samples/sample_1.jpg") ``` ``` public func addVideoFileURIToSourceSet(_ id: DesignBlockID, property: String, uri: URL) async throws ``` Add a source to the `sourceSet` property of the given block. * `id`: The block whose source set should be set. * `property`: The name of the property to modify. * `uri`: The source to add to the source set. ``` let videoFill = try engine.block.createFill(.video) try await engine.block.addVideoFileURIToSourceSet(videoFill, property: "fill/video/sourceSet", uri: "https://img.ly/static/example-assets/sourceset/1x.mp4") ``` Variables - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/variables?language=swift&platform=ios#setup # Variables In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify variables through the `variable` API. The `variable` API lets you set or get the contents of variables that exist in your scene. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Functions ``` public func findAll() -> [String] ``` Get all text variables currently stored in the engine. * Returns: Return a list of variable names. ``` let variableNames = engine.variable.findAll() ``` ``` public func set(key: String, value: String) throws ``` Set a text variable. * `key`: The variable's key. * `value`: The text to replace the variable with. ``` try engine.variable.set(key: "name", value: "Chris") ``` ``` public func get(key: String) throws -> String ``` Get a text variable. * `key:`: The variable's key. * Returns: The text value of the variable. ``` let name = try engine.variable.get(key: "name") // Chris ``` ``` public func remove(key: String) throws ``` Destroy a text variable. * `key:`: The variable's key. ``` try engine.variable.remove(key: "name") ``` ``` public func referencesAnyVariables(_ id: DesignBlockID) throws -> Bool ``` Checks whether the given block references any variables. Doesn't check the block's children. * `id:`: The block to inspect. * Returns: `true` if the block references variables and `false` otherwise. ``` let referencesVariables = try engine.block.referencesAnyVariables(block) ``` ## Localizing Variable Keys (CE.SDK only) You can show localized labels for the registered variables to your users by adding a corresponding label property to the object stored at `i18n..variables..label` in the configuration. Otherwise, the name used in `variable.setString()` will be shown. ![](/docs/cesdk/53ae2ef96327bbd7ef5994cdfe73e792/variables-dark.png) Using the Photo Editor - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/mobile-editor/solutions/photo-editor/?platform=ios&language=swift # Using the Photo Editor `Photo Editor` is built to provide versatile photo editing capabilities. Toggling from edit and preview modes enables users to evaluate their edited photo before export. A dock at the bottom of the editor provides quick access to the most essential photo editing tools allowing users to tweak adjustments, crop the photo, and add filters, effects, blur, text, shapes, as well as stickers. In this example, we will show you how to initialize the `Photo Editor` solution for the mobile editor on iOS. The mobile editor is implemented entirely with SwiftUI and this example assumes that you also use SwiftUI to integrate it, however, you can check the UIKit implementation sample on the [quickstart](/docs/cesdk/mobile-editor/quickstart?framework=uikit) page. It can be also applied to this example. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-solutions-photo-editor/PhotoEditorSolution.swift). * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-solutions-photo-editor/PhotoEditorSolution.swift) ## Import After [adding the IMGLYUI Swift Package](/docs/cesdk/mobile-editor/quickstart/#using-swift-package-manager) to your app. You can get started right away by importing the editor module into your own code. ``` import IMGLYEngine import IMGLYPhotoEditor ``` ## Initialization The editor is initialized with `EngineSettings` which are used to initialize the underlying [Engine](/docs/cesdk/engine/quickstart/). The license key that you received from IMG.LY is the only required parameter. Additionally, you should provide an optional unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU) and it is especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. For more details on how to configure the editor, visit the [configuration](/docs/cesdk/mobile-editor/configuration/) page. ``` let settings = EngineSettings(license: secrets.licenseKey, userID: "") var editor: some View { PhotoEditor(settings) ``` ## Presentation In this integration example the editor is presented as a modal view after tapping a button. Check out the [quickstart](/docs/cesdk/mobile-editor/quickstart/#environment) page for details on the expected environment for the editor and the `ModalEditor` helper. ``` @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } ``` ## Callbacks The photo editor should be used with custom [callbacks](/docs/cesdk/mobile-editor/configuration/callbacks/) to fit your use case. The `onCreate` callback is required to create a scene with just a single page with an image fill applied. This page is the photo you are editing. Its size can be changed to define a custom crop format. Per default, `OnCreateEditor.loadImage` with a white image is used. The default `onExport` callback exports a PNG compressed image, writes the content into a temporary file, and opens a system dialog for sharing the exported file. In this example, we changed the exported format to JPEG. ``` .imgly.onCreate { engine in // Create scene from image try await engine.scene.create(fromImage: Bundle.main.url(forResource: "sample_image", withExtension: "jpg")!) // Add asset sources try await engine.addDefaultAssetSources(baseURL: Engine.assetBaseURL) try await engine.addDemoAssetSources(sceneMode: engine.scene.getMode(), withUploadAssetSources: true) try await engine.asset.addSource(TextAssetSource(engine: engine)) let page = try engine.scene.getPages().first! // Define custom page (photo) size if needed try engine.block.setWidth(page, value: 1080) try engine.block.setHeight(page, value: 1080) // Assign image fill to page let image = try engine.block.find(byType: .graphic).first! try engine.block.setFill(page, fill: engine.block.getFill(image)) try engine.block.destroy(image) } .imgly.onExport { engine, eventHandler in // Export photo let scene = try engine.scene.get()! let mimeType: MIMEType = .jpeg let data = try await engine.block.export(scene, mimeType: mimeType) // 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)) } ``` Configure the Mobile Camera - CE.SDK | IMG.LY Docs [ios/swiftui/swift] https://img.ly/docs/cesdk/mobile-camera/configuration/?platform=ios&language=swift&framework=swiftui # Configure the Mobile Camera In this example, we will show you how to configure the camera. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/camera-guides-configuration/ConfiguredCameraSolution.swift). * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/camera-guides-configuration/ConfiguredCameraSolution.swift) ## License All the basic configuration settings are part of the `EngineSettings` which are required to initialize the camera. ``` let settings = EngineSettings(license: secrets.licenseKey, userID: "") ``` * `license` – the license to activate the [Engine](/docs/cesdk/engine/quickstart/) with. ``` let settings = EngineSettings(license: secrets.licenseKey, ``` * `userID` – an optional unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU). Especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. Providing this aids in better data accuracy. The default value is `nil`. ``` userID: "") ``` ## Configuration You can optionally pass a `CameraConfiguration` struct to the `Camera` initializer. ``` let config = CameraConfiguration( recordingColor: .blue, highlightColor: .yellow, maxTotalDuration: 30, allowModeSwitching: true ) ``` * `recordingColor` – the color of the record button. ``` recordingColor: .blue, ``` * `highlightColor` – the highlight color of the delete and confirm buttons. ``` highlightColor: .yellow, ``` * `maxTotalDuration` – the total duration that the camera is allowed to record. ``` maxTotalDuration: 30, ``` * `allowModeSwitching` – Set to `false` to lock the camera into its initial mode. ``` allowModeSwitching: true ``` ## Mode You can optionally configure the initial mode of the camera. ### Available Modes * `.standard`: the regular camera. * `.dualCamera(layoutMode)`: records with both front and back camera at the same time. * `layoutMode` determines the layout of the two cameras. Available options are `.horizontal` and `.vertical`. * `.reaction(layoutMode, URL, positionsSwapped)`: records with the camera while playing back a video. * `layoutMode` determines the layout of the two cameras. Available options are `.horizontal` and `.vertical`. * `URL` the URL to video to record a reaction to. * `positionsSwapped` a boolean indicating if the video and camera feed should swap positions. By default, the video being reacted to is on the top/left (depending on `layoutMode`). ``` mode: .standard ``` Modify Crop - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-crop?language=swift&platform=ios#setup # Modify Crop In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a blocks crop through the `block` API. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Common Properties Common properties are properties that occur on multiple block types. For instance, fill color properties are available for all the shape blocks and the text block. That's why we built convenient setter and getter functions for these properties. So you don't have to use the generic setters and getters and don't have to provide a specific property path. There are also `has*` functions to query if a block supports a set of common properties. ### Crop Manipulate the cropping region of a block by setting its scale, rotation, and translation. ``` public func supportsCrop(_ id: DesignBlockID) throws -> Bool ``` Query if the given block has crop properties. * `id:`: The block to query. * Returns: `true`, if the block has crop properties. ``` try engine.block.supportsCrop(image) ``` ``` public func setCropScaleX(_ id: DesignBlockID, scaleX: Float) throws ``` Set the crop scale in x direction of the given design block. Required scope: "layer/crop" * `id`: The block whose crop should be set. * `scaleX`: The scale in x direction. ``` try engine.block.setCropScaleX(image, scaleX: 2.0) ``` ``` public func setCropScaleY(_ id: DesignBlockID, scaleY: Float) throws ``` Set the crop scale in y direction of the given design block. Required scope: "layer/crop" * `id`: The block whose crop should be set. * `scaleY`: The scale in y direction. ``` try engine.block.setCropScaleY(image, scaleY: 1.5) ``` ``` public func setCropScaleRatio(_ id: DesignBlockID, scaleRatio: Float) throws ``` Set the crop scale ratio of the given design block. This will uniformly scale the content up or down. The center of the scale operation is the center of the crop frame. Required scope: "layer/crop" * `id`: The block whose crop should be set. * `scaleRatio`: The crop scale ratio. ``` try engine.block.setCropScaleRatio(image, scaleRatio: 3.0) ``` ``` public func setCropRotation(_ id: DesignBlockID, rotation: Float) throws ``` Set the crop rotation of the given design block. Required scope: "layer/crop" * `id`: The block whose crop should be set. * `rotation`: The rotation in radians. ``` try engine.block.setCropRotation(image, rotation: .pi) ``` ``` public func setCropTranslationX(_ id: DesignBlockID, translationX: Float) throws ``` Set the crop translation in x direction of the given design block. Required scope: "layer/crop" * `id`: The block whose crop should be set. * `translationX`: The translation in x direction. ``` try engine.block.setCropTranslationX(image, translationX: -1.0) ``` ``` public func setCropTranslationY(_ id: DesignBlockID, translationY: Float) throws ``` Set the crop translation in y direction of the given design block. Required scope: "layer/crop" * `id`: The block whose crop should be set. * `translationY`: The translation in y direction. ``` try engine.block.setCropTranslationY(image, translationY: 1.0) ``` ``` public func adjustCropToFillFrame(_ id: DesignBlockID, minScaleRatio: Float) throws ``` Adjust the crop position/scale to at least fill the crop frame. Required scope: "layer/crop" * `id`: The block whose crop scale ratio should be queried. * `minScaleRatio`: The minimal crop scale ratio to go down to. ``` try engine.block.adjustCropToFillFrame(image, minScaleRation: 1.0) ``` ``` public func setContentFillMode(_ id: DesignBlockID, mode: ContentFillMode) throws ``` Set a block's content fill mode. Required scope: "layer/crop" * `id`: The block to update. * `mode`: The content fill mode. ``` try engine.block.setContentFillMode(image, mode: .contain) ``` ``` public func resetCrop(_ id: DesignBlockID) throws ``` Resets the manually set crop of the given design block. The block's content fill mode is set to `.cover`. If the block has a fill, the crop values are updated so that it covers the block. Required scope: "layer/crop" * `id:`: The block whose crop should be reset. ``` try engine.block.resetCrop(image) ``` ``` public func getCropScaleX(_ id: DesignBlockID) throws -> Float ``` Get the crop scale on the x axis of the given design block. * `id:`: The block whose scale should be queried. * Returns: The scale on the x axis. ``` try engine.block.getCropScaleX(image) ``` ``` public func getCropScaleY(_ id: DesignBlockID) throws -> Float ``` Get the crop scale on the x axis of the given design block. * `id:`: The block whose scale should be queried. * Returns: The scale on the y axis. ``` try engine.block.getCropScaleY(image) ``` ``` public func flipCropHorizontal(_ id: DesignBlockID) throws ``` Adjusts the crop in order to flip the content along its own horizontal axis. * `id:`: The block whose crop should be updated. ``` try engine.block.flipCropHorizontal(image) ``` ``` public func flipCropVertical(_ id: DesignBlockID) throws ``` Adjusts the crop in order to flip the content along its own vertical axis. * `id:`: The block whose crop should be updated. ``` try engine.block.flipCropVertical(image) ``` ``` public func getCropScaleRatio(_ id: DesignBlockID) throws -> Float ``` Get the crop scale ratio of the given design block. * `id:`: The block whose crop scale ratio should be queried. * Returns: The crop scale ratio. ``` try engine.block.getCropScaleRatio(image) ``` ``` public func getCropRotation(_ id: DesignBlockID) throws -> Float ``` Get the crop rotation of the given design block. * `id:`: The block whose crop rotation should be queried. * Returns: The crop rotation. ``` try engine.block.getCropRotation(image) ``` ``` public func getCropTranslationX(_ id: DesignBlockID) throws -> Float ``` Get the crop translation on the x axis of the given design block. * `id:`: The block whose translation should be queried. * Returns: The translation on the x axis. ``` try engine.block.getCropTranslationX(image) ``` ``` public func getCropTranslationY(_ id: DesignBlockID) throws -> Float ``` Get the crop translation on the y axis of the given design block. * `id:`: The block whose translation should be queried. * Returns: The translation on the y axis. ``` try engine.block.getCropTranslationY(image) ``` ``` public func supportsContentFillMode(_ id: DesignBlockID) throws -> Bool ``` Query if the given block has a content fill mode. * `id:`: The block to query. * Returns: `true`, if the block has a content fill mode. ``` try engine.block.supportsContentFillMode(image) ``` ``` public func getContentFillMode(_ id: DesignBlockID) throws -> ContentFillMode ``` Query a block's content fill mode. * `id:`: The block to query. * Returns: The current mode. ``` try engine.block.getContentFillMode(image) ``` CreativeEngine APIs - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api?language=swift&platform=ios#accessing-the-creativeengine-apis Platform Web Node.JS iOS Catalyst macOS Android Language Swift Platform: iOS Language: Swift # CreativeEngine APIs Use the CreativeEngine APIs of the CreativeEditor SDK to implement highly customized workflows The APIs of the CreativeEngine allow you to programmatically manipulate scenes inside the editor to automate workflows and customize the user experience. ## Accessing the CreativeEngine APIs You can access the CreativeEngine APIs via the `engine` object and interact with the scene seen on screen. The examples in the API Guides will use the headless CreativeEngine: ``` import IMGLYEngine import SwiftUI struct IntegrateWithSwiftUI: View { @StateObject private var engine = Engine() var body: some View { ZStack { Canvas(engine: engine) Button("Use the Engine") { Task { 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) try? engine.block.find(byType: .text).forEach { id in try? engine.block.setOpacity(id, value: 0.5) } } } } } } ``` ## API Guides **[Scene](/docs/cesdk/engine/api/scene/)** Load, create, and save scenes or control the zoom. **[Block](/docs/cesdk/engine/api/block/)** Manipulate blocks, the elements a scene is made of, in various ways. **[Editor](/docs/cesdk/engine/api/editor/)** Control settings or history and observe state changes in your engine instance. **[Asset](/docs/cesdk/engine/api/assets/)** Manage assets by creating and reading from AssetSources. **[Event](/docs/cesdk/engine/api/events/)** Subscribe to block creation, update and destruction events. **[Variable](/docs/cesdk/engine/api/variables/)** Manage the values of pre-defined variables, allowing for quick customization of things like headlines. [ Previous Export with underlayer ](/docs/cesdk/engine/guides/underlayer/)[ Next Overview ](/docs/cesdk/engine/api/scene/) Text - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-text?language=swift&platform=ios#setup # Text In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to edit ranges within text blocks. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ``` public func replaceText(_ id: DesignBlockID, text: String, in subrange: Range? = 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. ``` try engine.block.replaceText(text, text: "Hello World") ``` ``` 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. ``` try engine.block.removeText(text, from: "Hello World".range(of: "Hello ")!) ``` ``` 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. ``` try engine.block.setTextColor( ``` ``` 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. ``` let colorsInRange = try engine.block.getTextColors(text) ``` ``` 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. ``` try engine.block.setTextFontWeight(text, fontWeight: .bold, "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex) ``` ``` 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. ``` let fontWeights = try engine.block.getTextFontWeights(text) ``` ``` 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. ``` try engine.block.setTextFontSize(text, fontSize: 14, "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex) ``` ``` 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. ``` let fontSizes = try engine.block.getTextFontSizes(text) ``` ``` 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. ``` try engine.block.setTextFontStyle(text, fontStyle: .italic, "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex) ``` ``` 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. ``` let fontStyles = try engine.block.getTextFontStyles(text) ``` ``` 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. ``` try engine.block.setTextCase(text, textCase: .titlecase) ``` ``` 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. ``` let textCases = try engine.block.getTextCases(text) ``` ``` 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. ``` let canToggleBold = try engine.block.canToggleBoldFont(text) ``` ``` 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. ``` let canToggleItalic = try engine.block.canToggleItalicFont(text) ``` ``` 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. ``` try engine.block.toggleBoldFont(text) ``` ``` 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. ``` try engine.block.toggleItalicFont(text) ``` ``` 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. ``` 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!) ``` ``` 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. ``` try engine.block.setTypeface(text, typeface: typeface!, "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex) try engine.block.setTypeface(text, typeface: typeface!) ``` ``` 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. ``` let defaultTypeface = try engine.block.getTypeface(text) ``` ``` 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. ``` let typefaces = try engine.block.getTypefaces(text) ``` ``` 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. ``` let selectedRange = try engine.block.getTextCursorRange() ``` ``` public func getTextVisibleLineCount(_ id: DesignBlockID) throws -> 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. ``` let lineCount = try engine.block.getTextVisibleLineCount(text) ``` ``` 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. ``` let lineBoundingBox = try engine.block.getTextLineBoundingBoxRect(text, 0) ``` Get started with CE.SDK Engine - CE.SDK | IMG.LY Docs [ios/swiftui/swift] https://img.ly/docs/cesdk/engine/quickstart/?platform=ios&language=swift&framework=swiftui # Get started with CE.SDK Engine In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s engine in your iOS app. The engine enables you to power your own UI and creative workflows. Whether you want to keep your current UI or design a new one from scratch, our API can do the heavy lifting for you no matter what your starting point is. Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0) ### Requirements Creative Engine requires iOS 14 and Swift 5.10 (Xcode 15.4) or later. ### Using Swift Package Manager If you use [Swift Package Manager](https://github.com/apple/swift-package-manager) to build your app and want to integrate the Creative Engine module using your regular workflows, add the [IMGLYEngine Swift Package](https://github.com/imgly/IMGLYEngine-swift) as a dependency to your project. ![](/docs/cesdk/f791cde10b12204d724306eea14c9f99/spm-engine.png) ### Using Cocoapods Creative Engine is also available through [CocoaPods](https://cocoapods.org). To install it, simply add the following line to your Podfile: `pod 'IMGLYEngine'`. Then run `pod install --repo-update` to install the latest version of the SDK. ## Import You can get started right away by importing the CESDK module into your own code. ``` import IMGLYEngine ``` ## Initialization To initialize `IMGLYEngine` for SwiftUI call the awaitable `Engine(license: userID:)` initializer. Once the engine is ready pass it to your SwiftUI view. ### Licensing In the awaitable initializer there are 2 arguments that are tied to your licensing. * `license` - an API key that you downloaded from our dashboard. * `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. If the license is invalid, the engine will not start and throw an error. ``` Group { if let engine { ContentView(engine: engine) } else { ProgressView() } } .onAppear { Task { engine = try await Engine(license: secrets.licenseKey, userID: "") } } ``` Use [`@StateObject`](https://developer.apple.com/documentation/swiftui/stateobject) property wrapper in your SwiftUI view to store the engine instance and then use it to create a `Canvas` view. ``` struct ContentView: View { @StateObject private var engine: Engine init(engine: Engine) { _engine = .init(wrappedValue: engine) } var body: some View { ZStack { Canvas(engine: engine) Button("Use the Engine") { Task { 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) try engine.block.find(byType: .text).forEach { id in try engine.block.setOpacity(id, value: 0.5) } } } } } } ``` ## Use the Creative Engine After these setup steps, you can use our APIs to interact with the Creative Engine. [The next couple of pages](/docs/cesdk/engine/guides/) will document the methods available. ``` Task { 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) try engine.block.find(byType: .text).forEach { id in try engine.block.setOpacity(id, value: 0.5) } } ``` Save Scenes to a String - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/guides/save-scene-to-string?language=swift&platform=ios#warning # Save Scenes to a String In this example, we will show you how to save scenes as a string with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). Explore a full code sample on [Github](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-save-scene-to-string/SaveSceneToString.swift) . * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/engine-guides-save-scene-to-string/SaveSceneToString.swift) CreativeEditor SDK allows you to save scenes in a binary format to share them between editors or store them for later editing. This is done by converting the contents of a scene to a single string, which can then be stored or transferred. #### Warning A _scene file_ does not include any fonts or images. Only the source URIs of assets, the general layout, and element properties are stored. When loading scenes in a new environment, ensure previously used asset URIs are available. To get hold of such a string, you need to use `engine.scene.saveToString()`. This is an asynchronous method. After waiting for the coroutine to finish, we receive a plain string holding the entire scene currently loaded in the editor. This includes all pages and any hidden elements, but none of the actual asset data. ``` let sceneAsString = try await engine.scene.saveToString() ``` The returned string consists solely of ASCII characters and can safely be used further or written to a database. ``` print(sceneAsString) ``` Scene Lifecycle - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/scene-lifecycle?language=swift&platform=ios#setup # Scene Lifecycle In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to save scenes through the `scene` API and export them to PNG. At any time, the engine holds only a single scene. Loading or creating a scene will replace the current one. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Creating a Scene ``` @discardableResult public func create(sceneLayout: SceneLayout = .free) throws -> DesignBlockID ``` Create a new scene, along with its own camera. * `sceneLayout`: The desired layout of the scene. * Returns: The scenes handle. ``` var scene = try engine.scene.create() ``` ``` public func get() throws -> DesignBlockID? ``` Return the currently active scene. * Returns: The scene or nil, if none was created yet. ``` scene = try engine.scene.get()! ``` ``` @discardableResult public func createVideo() throws -> DesignBlockID ``` Create a new scene in video mode, along with its own camera. * Returns: The scene's handle. ``` scene = try engine.scene.createVideo() ``` ``` public func getMode() throws -> SceneMode ``` Get the current scene mode. * Returns: The current mode of the scene. ``` let mode = try engine.scene.getMode() ``` ``` @discardableResult public func create(fromImage url: URL, dpi: Float = 300, pixelScaleFactor: Float = 1, sceneLayout: SceneLayout = .free) async throws -> DesignBlockID ``` Loads the given image and creates a scene with a single page showing the image. Fetching the image may take an arbitrary amount of time, so the scene isn't immediately available. * `url`: The image URL. * `dpi`: The scene's DPI. * `pixelScaleFactor`: The display's content scale factor. * `sceneLayout`: The desired layout of the scene. * Returns: A handle to the loaded scene. ``` scene = try await engine.scene.create(fromImage: .init(string: "https://img.ly/static/ubq_samples/sample_4.jpg")!) ``` ``` @discardableResult public func create(fromVideo url: URL) async throws -> DesignBlockID ``` Loads the given video and creates a scene with a single page showing the video. Fetching the video may take an arbitrary amount of time, so the scene isn't immediately available. * `url`: The video URL. * Returns: A handle to the loaded scene. ``` scene = try await engine.scene.create(fromVideo: .init(string: "https://img.ly/static/ubq_video_samples/bbb.mp4")!) ``` ## Loading a Scene ``` @discardableResult public func load(from string: String) async throws -> DesignBlockID ``` Load the contents of a scene file. * `string:`: The scene file contents, a base64 string. * Returns: A handle to the loaded scene. ``` scene = try await engine.scene.load(from: SCENE_CONTENT) ``` ``` @discardableResult public func load(from url: URL) async throws -> DesignBlockID ``` Load a scene from the URL to the scene file. The scene file will be fetched asynchronously by the engine. * `url:`: The URL of the scene file. * Returns: A handle to the loaded scene. ``` scene = try await engine.scene ``` ``` @discardableResult public func loadArchive(from url: URL) async throws -> DesignBlockID ``` Load a scene from the URL to the previously archived scene file. The scene file will be fetched asynchronously by the engine. * `url:`: The URL of the archived scene file. * Returns: A handle to the loaded scene. ``` scene = try await engine.scene ``` ## Saving a Scene ``` public func saveToString(allowedResourceSchemes: [String] = ["blob", "bundle", "file", "http", "https"]) async throws -> String ``` Serializes the current scene into a string. Selection is discarded. * `allowedResourceSchemes:`: If a resource URL has a scheme that is not in this list an error will be thrown. * Returns: A serialized scene string. ``` let string = try await engine.scene.saveToString() ``` ``` public func saveToArchive() async throws -> Blob ``` Saves the current scene and all of its referenced assets into an archive. The archive contains all assets, that were accessible when this function was called. Blocks in the archived scene reference assets relative from to the location of the scene file. These references are resolved when loading such a scene via `load(from url:)`. * Returns: A serialized scene data blob. ``` let archive = try await engine.scene.saveToArchive() ``` ## Events ``` public var onActiveChanged: AsyncStream { get } ``` Subscribe to changes to the active scene rendered by the engine. ``` let task = Task { for await _ in engine.scene.onActiveChanged { let newActiveScene = try engine.scene.get() } } ``` Using the Apparel Editor - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/mobile-editor/solutions/apparel-editor/?platform=ios&language=swift # Using the Apparel Editor `Apparel Editor` is a custom, mobile apparel UI for creating a print-ready design. The editable page is overlaid on a t-shirt mockup to give users an idea of where to position elements. Toggling from edit to preview mode allows reviewing the design in the context of the entire t-shirt. A floating action button at the bottom of the editor features only the most essential editing options in order of relevance allowing users to overlay text, add images, shapes, stickers and upload new image assets. In this example, we will show you how to initialize the `Apparel Editor` solution for the mobile editor on iOS. The mobile editor is implemented entirely with SwiftUI and this example assumes that you also use SwiftUI to integrate it, however, you can check the UIKit implementation sample on the [quickstart](/docs/cesdk/mobile-editor/quickstart?framework=uikit) page. It can be also applied to this example. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-solutions-apparel-editor/ApparelEditorSolution.swift). * [View on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v1.43.0/editor-guides-solutions-apparel-editor/ApparelEditorSolution.swift) ## Import After [adding the IMGLYUI Swift Package](/docs/cesdk/mobile-editor/quickstart/#using-swift-package-manager) to your app. You can get started right away by importing the editor module into your own code. ``` import IMGLYApparelEditor ``` ## Initialization The editor is initialized with `EngineSettings` which are used to initialize the underlying [Engine](/docs/cesdk/engine/quickstart/). The license key that you received from IMG.LY is the only required parameter. Additionally, you should provide an optional unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU) and it is especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. For more details on how to configure the editor, visit the [configuration](/docs/cesdk/mobile-editor/configuration/) page. ``` let settings = EngineSettings(license: secrets.licenseKey, userID: "") var editor: some View { ApparelEditor(settings) } ``` ## Presentation In this integration example the editor is presented as a modal view after tapping a button. Check out the [quickstart](/docs/cesdk/mobile-editor/quickstart/#environment) page for details on the expected environment for the editor and the `ModalEditor` helper. ``` @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } ``` Effects & Filters - CE.SDK | IMG.LY Docs [ios/undefined/swift] https://img.ly/docs/cesdk/engine/api/block-effects?language=swift&platform=ios#setup # Effects & Filters In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a block's effects through the `block` API. Effects can be added to any shape or page. You can create and configure multiple effects and apply them to blocks. Effects will be applied in the order they are appended to the block's effects list. If the same effect appears multiple times in the list it will also be applied multiple times. ## Setup This example uses the headless CreativeEngine. See the [Setup](/docs/cesdk/engine/quickstart/) article for a detailed guide. To get started right away, you can also access the `block` API within a running CE.SDK instance via `cesdk.engine.block`. Check out the [APIs Overview](../) to see that illustrated in more detail. ## Creating an Effect To create an effect simply use `createEffect`. The available types are described [here](/docs/cesdk/engine/api/block-effect-types/) and range from LUT based color filters to advanced effects. ``` let pixelize = try engine.block.createEffect(.pixelize) ``` ``` public func createEffect(_ type: EffectType) throws -> DesignBlockID ``` Create a new effect. * `type:`: The type of the effect object that shall be created. * Returns: The created effect's handle. ``` let pixelize = try engine.block.createEffect(.pixelize) ``` We currently support the following effect types: * `EffectType.adjustments` * `EffectType.crossCut` * `EffectType.dotPattern` * `EffectType.duotoneFilter` * `EffectType.extrudeBlur` * `EffectType.glow` * `EffectType.greenScreen` * `EffectType.halfTone` * `EffectType.linocut` * `EffectType.liquid` * `EffectType.lutFilter` * `EffectType.mirror` * `EffectType.outliner` * `EffectType.pixelize` * `EffectType.posterize` * `EffectType.radialPixel` * `EffectType.recolor` * `EffectType.sharpie` * `EffectType.shifter` * `EffectType.tiltShift` * `EffectType.tvGlitch` * `EffectType.vignette` ``` let pixelize = try engine.block.createEffect(.pixelize) ``` ## Configuring an Effect You can configure effects just like you configure design blocks. See [Modify Properties](/docs/cesdk/engine/api/block-properties/) for more detail. ``` try engine.block.setInt(pixelize, property: "pixelize/horizontalPixelSize", value: 5) ``` ## Adding an Effect ``` public func appendEffect(_ id: DesignBlockID, effectID: DesignBlockID) throws ``` Inserts an effect at the end of the list of effects. The same effect can appear multiple times in the list and won't be removed if appended again. Required scope: "appearance/effect" * `id`: The block to append the effect to. * `effectID`: The effect to append. ``` try engine.block.appendEffect(block, effectID: pixelize) ``` ``` public func supportsEffects(_ id: DesignBlockID) throws -> Bool ``` Queries whether the block supports effects. * `id:`: The block to query. * Returns: `true`, if the block can render effects, `false` otherwise. ``` if try engine.block.supportsEffects(block) { ``` ## Managing the Order of Effects ``` public func insertEffect(_ id: DesignBlockID, effectID: DesignBlockID, index: Int) throws ``` Inserts an effect at the given index into the list of effects of the given block. The same effect can appear multiple times in the list and won't be removed if appended again. Required scope: "appearance/effect" * `id`: The block to update. * `effectID`: The effect to insert. * `index`: The index at which the effect shall be inserted. ``` try engine.block.insertEffect(block, effectID: pixelize, index: 0) ``` ``` public func removeEffect(_ id: DesignBlockID, index: Int) throws ``` Removes the effect at the given index. Required scope: "appearance/effect" * `id`: The block to remove the effect from. * `index`: The index where the effect is stored. ``` try engine.block.removeEffect(block, index: 1) ``` ``` public func getEffects(_ id: DesignBlockID) throws -> [DesignBlockID] ``` Get a list of all effects attached to this block. * `id:`: The block to query. * Returns: A list of effects or an error, if the block doesn't support effects. ``` let effects = try engine.block.getEffects(block) ``` ## Enabling and Disabling Effects ``` public func setEffectEnabled(effectID: DesignBlockID, enabled: Bool) throws ``` Sets the enabled state of an 'effect' block. * `effectID`: The 'effect' block to update. * `enabled`: The new state. ``` try engine.block.setEffectEnabled(effectID: effects[0], enabled: false) ``` ``` public func isEffectEnabled(effectID: DesignBlockID) throws -> Bool ``` Queries whether an 'effect' block is enabled and therefore applies its effect. * `effectID:`: The 'effect' block to query. * Returns: `true`, if the effect is enabled. `false` otherwise. ``` try engine.block.isEffectEnabled(effectID: effects[0]) ```