Turn on the built-in gesture that swaps two images by dragging one block onto another, and place a block created from an asset at a point you choose.
The examples below use a page with two image blocks:
let scene = try engine.scene.create()
let page = try engine.block.create(.page)try engine.block.setWidth(page, value: 800)try engine.block.setHeight(page, value: 600)try engine.block.appendChild(to: scene, child: page)
// The gesture moves image fills, so give each block a rectangle shape and an image fill.func addImage(_ uri: URL, x: Float) throws { let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) let fill = try engine.block.createFill(.image) try engine.block.setURL(fill, property: "fill/image/imageFileURI", value: uri) try engine.block.setFill(block, fill: fill) try engine.block.setWidth(block, value: 300) try engine.block.setHeight(block, value: 300) try engine.block.setPositionX(block, value: x) try engine.block.setPositionY(block, value: 150) try engine.block.appendChild(to: page, child: block)}
try addImage(firstImageURL, x: 50)try addImage(secondImageURL, x: 450)The swap gesture#
Two settings turn the gesture on and set how long the pointer stays pressed before the image lifts:
// Let users exchange two images by dragging one image block onto another.try engine.editor.setSettingBool("dragToSwapFills/enabled", value: true)
// How long the pointer stays pressed before the image lifts.// Use 0 for fixed layouts, where every drag must swap instead of move.try engine.editor.setSettingFloat("dragToSwapFills/longPressDurationMs", value: 500)The engine owns the whole interaction. The user presses and holds an image block, drags it over another one, which is highlighted as the drop target, and releases to exchange the two images. A completed swap adds one undo step. The default duration is 500 milliseconds. A duration of 0 lifts at once, which suits a fixed layout where a drag must never move a block. On iOS, the Design, Photo, Video, Postcard and Apparel Starter Kits turn the gesture on.
A block can lift or receive an image when it has an image fill and its fill/change scope is allowed. A sticker and an animated sticker are not eligible. A block whose editor/select scope is denied takes no part either: a press or a release over it reaches the block below it, as a tap does. A page never takes part in a swap. The gesture runs only in the transform edit mode.
The engine view draws the lifted image at the pointer, so there is nothing to render. When the image cannot load, it shows a plain card. On iOS, the device gives haptic feedback when the image lifts. On macOS, the engine receives a mouse press only after the pointer starts to move, so a press and hold without movement does not lift the image. Set dragToSwapFills/longPressDurationMs to 0 there to swap on a plain drag.
The engine also ends the drag when the system cancels the touch, so there is nothing to cancel by hand.
Placing an asset#
Register a source and fetch one asset from it:
try engine.asset.addLocalSource(sourceID: "my-images")try engine.asset.addAsset( to: "my-images", asset: AssetDefinition( id: "sample-1", meta: [ "uri": firstImageURL.absoluteString, "fillType": FillType.image.rawValue, ], label: ["en": "Sample image"], ),)
guard let asset = try await engine.asset.fetchAsset(sourceID: "my-images", assetID: "sample-1") else { return nil}apply(sourceID:assetResult:placement:) creates the block and places it in one call:
// Add the asset to `page`, centered at (200, 300) relative to `page`.guard let placed = try await engine.asset.apply( sourceID: "my-images", assetResult: asset, placement: AssetPlacement(parent: page, center: CGPoint(x: 200, y: 300)),) else { return nil}print("Placed block \(placed)")AssetPlacement carries a parent and a center. The center is relative to parent, and both coordinates must be finite. An infinite or NaN coordinate is an error.
defaultApplyAsset(assetResult:placement:) does the same without a source. Both fields of AssetPlacement are optional. A nil parent adds the block to the current page, and a nil center lets the engine place the block itself:
// An omitted `parent` adds the block to the current page.if let defaultPlaced = try await engine.asset.defaultApplyAsset( assetResult: asset, placement: AssetPlacement(center: CGPoint(x: 600, y: 300)),) { print("Placed block \(defaultPlaced)")}A source that was registered with its own applyAsset function ignores the placement. That source decides where its block goes.
An asset that holds a transform preset or a scene replaces the page or the scene, so it uses neither the parent nor the center. An archived block collection keeps its own positions and uses only the parent.
The engine view takes no drags from outside the canvas. To add an asset at a point the user picks, apply it with AssetPlacement.
API Reference#
Methods#
| Method | Description |
|---|---|
engine.scene.create() |
Create an empty scene |
engine.block.create(_:) |
Create a page or a graphic block |
engine.block.setWidth(_:value:) |
Set the width of a block |
engine.block.setHeight(_:value:) |
Set the height of a block |
engine.block.appendChild(to:child:) |
Add a block to a parent |
engine.block.createShape(_:) |
Create a shape |
engine.block.setShape(_:shape:) |
Give a graphic block its shape |
engine.block.createFill(_:) |
Create a fill |
engine.block.setURL(_:property:value:) |
Set the image of an image fill |
engine.block.setFill(_:fill:) |
Give a block its fill |
engine.block.setPositionX(_:value:) |
Set the horizontal position of a block |
engine.block.setPositionY(_:value:) |
Set the vertical position of a block |
engine.editor.setSettingBool(_:value:) |
Turn the swap gesture on or off |
engine.editor.setSettingFloat(_:value:) |
Set how long the press lasts before the image lifts |
engine.asset.addLocalSource(sourceID:) |
Register a local asset source |
engine.asset.addAsset(to:asset:) |
Add an asset to a local source |
engine.asset.fetchAsset(sourceID:assetID:) |
Fetch one asset from a source |
engine.asset.apply(sourceID:assetResult:placement:) |
Create a block from an asset at a given placement |
engine.asset.defaultApplyAsset(assetResult:placement:) |
The same, without the source’s own applyAsset |
Properties#
| Property | Type | Default | Description |
|---|---|---|---|
dragToSwapFills/enabled |
Bool | false |
Turns the press-and-hold swap gesture on |
dragToSwapFills/longPressDurationMs |
Float | 500 |
Hold in milliseconds before the image lifts. 0 lifts at once, which suits fixed layouts |
Key types#
| Type | Property | Type | Description |
|---|---|---|---|
AssetPlacement |
parent |
DesignBlockID? |
The block the new block is added to; nil is the current page |
AssetPlacement |
center |
CGPoint? |
The center relative to parent; nil lets the engine place it |