Search Docs
Loading...
Skip to content

Drag & Drop

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.

5 mins
estimated time
GitHub

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