Overview
Touch handlers in Open Mobile Maps manage user gestures including taps, pans, pinches, and other touch interactions. The SDK provides a default implementation with standard map controls, but you can customize or replace it entirely for specialized interactions.
TouchHandlerInterface
The core interface for handling touch events:
class TouchHandlerInterface {
public:
virtual void onTouchEvent ( const TouchEvent & touchEvent ) = 0 ;
virtual void insertListener ( const std :: shared_ptr < TouchInterface > & listener , int32_t index ) = 0 ;
virtual void addListener ( const std :: shared_ptr < TouchInterface > & listener ) = 0 ;
virtual void removeListener ( const std :: shared_ptr < TouchInterface > & listener ) = 0 ;
}
Touch Event Structure
Touch events contain information about pointer positions and actions:
struct TouchEvent {
std ::vector < Vec2F > pointers; // Positions of all active touch points
float scrollDelta; // Scroll wheel delta
TouchAction touchAction; // DOWN, MOVE, UP, or CANCEL
}
Touch Actions
DOWN - Touch began
MOVE - Touch moved
UP - Touch ended normally
CANCEL - Touch was cancelled (e.g., interrupted by system)
DefaultTouchHandler
The SDK includes a default touch handler with standard map interactions:
Creating the Default Handler
val touchHandler = DefaultTouchHandlerInterface. create (
scheduler = mapView.scheduler,
density = resources.displayMetrics.density
)
Touch Handler States
The default handler implements a state machine for gesture recognition:
enum TouchHandlingState {
IDLE ,
ONE_FINGER_DOWN ,
ONE_FINGER_MOVING ,
ONE_FINGER_UP_AFTER_CLICK ,
ONE_FINGER_DOUBLE_CLICK_DOWN ,
ONE_FINGER_DOUBLE_CLICK_MOVE ,
TWO_FINGER_DOWN ,
TWO_FINGER_MOVING ,
ONE_FINGER_AFTER_TWO
}
Gesture Recognition
The default handler recognizes and processes:
Single tap - Click interaction
Long press - Press and hold (500ms default)
Pan - Single finger drag
Pinch zoom - Two finger zoom
Two finger pan - Two finger drag (rotation/tilt on 3D maps)
Double tap - Quick double click (300ms timeout)
Scroll - Mouse wheel zoom
Configuration Constants
int32_t TWO_FINGER_TOUCH_TIMEOUT = 100 ; // ms to recognize two-finger gesture
int32_t DOUBLE_TAP_TIMEOUT = 300 ; // ms between taps for double-tap
int32_t LONG_PRESS_TIMEOUT = 500 ; // ms to trigger long press
int32_t CLICK_DISTANCE_MM = 3 ; // Max movement to still be a click
Touch Listeners
Add custom listeners to respond to touch events:
TouchInterface
Implement this interface to receive touch callbacks:
class TouchInterface {
public:
virtual bool onClickConfirmed ( const Vec2F & coord ) = 0 ;
virtual bool onLongPress ( const Vec2F & coord ) = 0 ;
virtual bool onMove ( const Vec2F & deltaScreen , bool confirmed , bool doubleClick ) = 0 ;
virtual bool onTwoFingerClick ( const Vec2F & coord1 , const Vec2F & coord2 ) = 0 ;
virtual bool onTwoFingerMove ( const std :: vector < Vec2F > & posScreen ) = 0 ;
virtual void clearTouch () = 0 ;
}
Adding Listeners
Listeners are managed in priority order:
// Add listener with highest priority (index 0)
touchHandler. insertListener (customListener, 0 )
// Add listener at end
touchHandler. addListener (customListener)
// Remove listener
touchHandler. removeListener (customListener)
Listeners with lower index values have higher priority. Return true from a listener callback to consume the event and prevent lower-priority listeners from receiving it.
iOS Touch Handling
The iOS implementation bridges UIKit touch events to the touch handler:
class MCMapViewTouchHandler {
// Convert UITouch events to MCTouchEvent
func touchesBegan ( _ touches : Set <UITouch>, with event : UIEvent ? ) {
touchHandler. onTouchEvent (
activeTouches. asMCTouchEvent (
in : mapView,
scale : Float (mapView. contentScaleFactor ),
action : . DOWN
)
)
}
func touchesMoved ( _ touches : Set <UITouch>, with event : UIEvent ? ) {
touchHandler. onTouchEvent (
activeTouches. asMCTouchEvent (
in : mapView,
scale : Float (mapView. contentScaleFactor ),
action : . MOVE
)
)
}
// Gesture recognizer support
func handlePan ( panGestureRecognizer : UIPanGestureRecognizer) {
let location = panGestureRecognizer. location ( in : mapView)
// Convert to touch event
}
func handlePinch ( pinchGestureRecognizer : UIPinchGestureRecognizer) {
// Simulate two-finger touch with scale
let s = pow (pinchGestureRecognizer. scale , 2.414 )
// Convert to touch event with synthetic touch points
}
}
Coordinate Conversion
iOS example of converting screen coordinates:
extension Set <UITouch> {
func asMCTouchLocation ( in view : UIView, scale : Float ) -> [MCVec2F] {
map {
let location = $0 . location ( in : view)
let x = Float (location. x ) * scale
let y = Float (location. y ) * scale
return MCVec2F ( x : x, y : y)
}
}
}
Custom Touch Handler Example
Create a custom handler for specialized interactions:
class CustomTouchHandler (
private val scheduler: SchedulerInterface ,
private val density: Float
) : TouchHandlerInterface {
private val listeners = mutableListOf < TouchInterface >()
private var lastTouchPosition: Vec2F ? = null
override fun onTouchEvent (touchEvent: TouchEvent ) {
when (touchEvent.touchAction) {
TouchAction.DOWN -> handleTouchDown (touchEvent.pointers[ 0 ])
TouchAction.MOVE -> handleTouchMove (touchEvent.pointers[ 0 ])
TouchAction.UP -> handleTouchUp (touchEvent.pointers[ 0 ])
TouchAction.CANCEL -> handleTouchCancel ()
}
}
private fun handleTouchDown (position: Vec2F ) {
lastTouchPosition = position
// Custom touch down logic
}
private fun handleTouchMove (position: Vec2F ) {
lastTouchPosition?. let { last ->
val delta = Vec2F (
position.x - last.x,
position.y - last.y
)
// Notify listeners
for (listener in listeners) {
if (listener. onMove (delta, true , false )) {
break // Event consumed
}
}
}
lastTouchPosition = position
}
override fun addListener (listener: TouchInterface ) {
listeners. add (listener)
}
override fun removeListener (listener: TouchInterface ) {
listeners. remove (listener)
}
override fun insertListener (listener: TouchInterface , index: Int ) {
listeners. add (index, listener)
}
}
Use Cases
Drawing Mode
Implement a custom touch handler for drawing on the map:
class DrawingTouchHandler : TouchHandlerInterface {
private val drawingPoints = mutableListOf < Vec2F >()
override fun onTouchEvent (touchEvent: TouchEvent ) {
when (touchEvent.touchAction) {
TouchAction.DOWN -> {
drawingPoints. clear ()
drawingPoints. add (touchEvent.pointers[ 0 ])
}
TouchAction.MOVE -> {
drawingPoints. add (touchEvent.pointers[ 0 ])
updateDrawingLayer (drawingPoints)
}
TouchAction.UP -> {
finalizeDrawing (drawingPoints)
}
}
}
}
Create a handler for distance measurement:
class MeasurementTouchListener : TouchInterface {
private val measurementPoints = mutableListOf < Coord >()
override fun onClickConfirmed (coord: Vec2F ): Boolean {
val mapCoord = convertToMapCoordinate (coord)
measurementPoints. add (mapCoord)
if (measurementPoints.size >= 2 ) {
val distance = calculateDistance (measurementPoints)
showMeasurementResult (distance)
}
return true // Consume event
}
}
Custom Gesture Recognition
Implement specialized gestures:
class CustomGestureHandler : MCTouchInterface {
func onMove ( _ deltaScreen : MCVec2F, confirmed : Bool , doubleClick : Bool ) -> Bool {
// Only respond to horizontal swipes
if abs (deltaScreen. x ) > abs (deltaScreen. y ) * 2 {
handleHorizontalSwipe (deltaScreen. x )
return true
}
return false // Let other handlers process
}
private func handleHorizontalSwipe ( _ delta : Float ) {
if delta > 50 {
// Swipe right action
} else if delta < -50 {
// Swipe left action
}
}
}
Best Practices
Return true from listener callbacks only when you want to prevent other handlers from processing the event
Use listener priority (index) to control which handlers get events first
Consider whether your custom interaction should block map navigation
Test interaction with multiple listeners to avoid conflicts
Touch coordinates are in screen pixels (scaled by device pixel ratio)
Convert to map coordinates using the camera interface when needed
Account for screen scale/density when calculating distances
Test on different device sizes and pixel densities
Be aware of platform-specific gesture recognizers (especially on iOS)
Consider disabling default gestures when implementing custom interactions
Provide clear visual feedback during custom gestures
Test edge cases like interrupted gestures and multi-finger interactions
Styling Customize colors, patterns, and visual properties
Layer Configuration Configure tiled layer settings and zoom levels