Skip to main content

Gestures

Gestures are a key part in making your UI interactive. Komplete UI offers two gestures, Tap & Drag, which can be combined to design complex user interactions.

Priority

The default priority is inherently specified through the visual stacking order of the components that the gestures are applied to. For a new gesture the input events are always sent first to the highest priority gesture giving it the opportunity to become active. As soon as a gesture becomes active no other gestures can respond to the user input until the gesture completes.

Here are the priority rules for different cases of applying multiple gestures:

Same Component

Mutliple gestures applied to the same component are prioritized in the order of definition:

Rectangle(...) with {
TapGesture(...) // highest priority
DragGesture(...)
}

Ancestor and Descendent Components

Since child components stack visually on top of their ancestors they have a higher priority:

ZStack {
HStack {
Rectangle(...) with { TapGesture(...) } // highest priority
}
}
with { DragGesture(...) }// lower priority

Sibling Components

Visually higher siblings take priority over lower ones:

ZStack {
Rectangle(...) with { TapGesture(...) }
Rectangle(...) with { DragGesture(...) } // higher priority
}

Combining Gestures

Even though only one gesture can be active at the same time it is possible to respond to different gestures. When combining gestures the priority of the different gestures, as well as how the drag threshold is configured can make a difference. Here are some key cases.

Same component

Let's have a look at how different user input can result in different gestures given this example:

Rectangle(...) with {
TapGesture(
down: fun (event: TapGestureEvent) { print("Tap Down") },
single: fun (event: TapGestureEvent) { print("Tap Single") },
cancel: fun () { print("Tap Cancelled") }
)
DragGesture(
start: fun (event: DragGestureEvent) { print("Drag Start") },
update: fun (event: DragGestureEvent) { print("Drag Update") },
complete: fun (event: DragGestureEvent) { print("Drag Complete") }
)
}

As a first step the user taps down into the Rectangle. This will trigger the start handler of the tap gesture, but this doesn't mean the gesture is active. We still don't know whether the intention of the user is to drag or simply tap. Both gestures are still observing the user input to decide whether or not to become active.

Scenario 1: The user ends the tap close to the starting location

This will complete the tap gesture and trigger the single handler. The drag gesture will not respond at all.

Scenario 2: The user moves the pointer beyond the drag gesture's threshold

Depending on how the threshold is configured, the first thing you might observe is the cancel handler being invoked, because tap gestures don't allow significant movement between a "down" and "up". In this case the default drag gesture threshold is used, since no explicit threshold value was defined. Once the movement exceeds the default threshold of the drag gesture, you'll notice its start handler is invoked, immediately followed by update. The complete handler is invoked once the user ends the input, e.g. by releasing the mouse.

Drag Gesture Without Threshold

Usually a drag gesture with a threshold of 0 starts immediately on "down", but there's an exception. If a higher priority tap gesture is found, the drag gesture will only start after the first movement. This allows the tap gesture to still respond if the user doesn't move between "down" & "up".

Rectangle(...) with {
TapGesture(...)
DragGesture(
minimum_distance: 0, // Gesture will start on pointer down
...
)
}

If the order is swapped, the drag gesture has higher priority and the tap gesture will never be able to start:

Rectangle(...) with {
DragGesture(minimum_distance: 0, ...)
TapGesture(...) // never triggered
}

Ancestor and Descendent Components

The same combination scenarios described for the Same Component work when two different gestures are applied to components that are on different levels of the hierarchy:

ZStack {
HStack {
Rectangle(...) with { TapGesture(...) } // highest priority
}
}
with { DragGesture(...) } // lower priority

Sibling Components

Combining gestures between siblings is not possible. Higher siblings will block gestures on lower ones. In the following a example the drag gesture on the first Rectangle will never start, even if the pointer is moved.

ZStack {
Rectangle(...) with { DragGesture(...) }
Rectangle(...) with { TapGesture(...) } // higher priority, blocks lower priority sibling
}