---
title: "Touch"
canonical_url: https://developer.repebble.com/guides/events-and-services/touch/
last_updated: 2026-10-11
sdk_version: 4.33.1
---
# Touch

Source: https://developer.repebble.com/guides/events-and-services/touch/

On hardware platforms with a touchscreen, the `TouchService` lets an app receive touchdown, lift-off, and position updates as the user moves their finger across the display. See the platform support table in [_Hardware Information_](https://developer.repebble.com/guides/tools-and-resources/hardware-information.md#platform-support) for which watches have a touchscreen. This is the same low-level event stream the system itself uses, so apps can build draggable UI or free-form input on top of it. Apps that want gestures rather than raw touches can use the built-in [gesture recognizers](#gesture-recognizers), and apps that just want their menus to scroll by touch can opt in to [touch navigation](#touch-navigation) without handling any touch events at all.

**Important**  

Touch input is currently **not supported in watchfaces**. While we work out how we want to expose it, touch is intentionally restricted to watchapps - it is easier to allow it later than to take it away once apps depend on it. For now, only use the `TouchService` from a watchapp.

## Detecting Touch Support

A touchscreen is not present on every platform, and even when it is the user can disable touch input from _Settings → Display → Touch_. Apps should call [`touch_service_is_enabled()`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#touch_service_is_enabled "touch\_service\_is\_enabled") before relying on touch input - typically from the window's `appear` handler - and gracefully degrade if it returns `false`:

```
static void main\_window\_appear(Window \*window) {if (!touch\_service\_is\_enabled()) {text\_layer\_set\_text(s\_status\_layer,"Touch is disabled. Enable it in Settings → Display.");return;}// Touch is available - subscribe and start the touch UItouch\_service\_subscribe(touch\_handler, NULL);}
```

`touch_service_is_enabled()` returns `false` on platforms without a touchscreen, so a single check covers both the "no hardware" and the "user-disabled" cases.

For code that should only be compiled on platforms with a touchscreen at all - for example, an entire gesture recognizer that has no equivalent on button-only hardware - use the `PBL_TOUCH` compile-time define:

```
#if defined(PBL\_TOUCH)touch\_service\_subscribe(touch\_handler, NULL);#else// Fall back to a button-based UIwindow\_set\_click\_config\_provider(window, click\_config\_provider);#endif
```

## Subscribing to Touch Events

Touch events are delivered through a [`TouchServiceHandler`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchServiceHandler "TouchServiceHandler") callback. The handler receives a pointer to a [`TouchEvent`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchEvent "TouchEvent") describing what happened, and the context pointer that was registered with the subscription:

```
static void touch\_handler(const TouchEvent \*event, void \*context) {switch (event-\>type) {case TouchEvent\_Touchdown:APP\_LOG(APP\_LOG\_LEVEL\_DEBUG, "Touchdown at %d, %d", event-\>x, event-\>y);break;case TouchEvent\_PositionUpdate:APP\_LOG(APP\_LOG\_LEVEL\_DEBUG, "Move to %d, %d", event-\>x, event-\>y);break;case TouchEvent\_Liftoff:APP\_LOG(APP\_LOG\_LEVEL\_DEBUG, "Liftoff at %d, %d", event-\>x, event-\>y);break;}}
```

Subscribing to the service powers on the touch sensor; it stays on as long as at least one app is subscribed and is automatically disabled again once the last subscriber drops:

```
// Receive touch eventstouch\_service\_subscribe(touch\_handler, NULL);
```

When the app no longer needs touch input - for example, when its main window disappears - unsubscribe:

```
touch\_service\_unsubscribe();
```

## Event Types

The [`TouchEventType`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchEventType "TouchEventType") field on each event identifies what the user just did:

| Event Type | Description |
| --- | --- |
| [`TouchEvent_Touchdown`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchEvent_Touchdown "TouchEvent\_Touchdown") | The user has just placed a finger on the screen. `x` and `y` are the initial contact position. |
| [`TouchEvent_PositionUpdate`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchEvent_PositionUpdate "TouchEvent\_PositionUpdate") | An existing touch has moved. `x` and `y` are the new position. |
| [`TouchEvent_Liftoff`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchEvent_Liftoff "TouchEvent\_Liftoff") | The user has just lifted their finger. `x` and `y` are the final position before lift-off. |

Coordinates are in the same screen-relative pixel space used everywhere else in the UI, so they can be passed directly to drawing routines or compared against [`Layer`](https://developer.repebble.com/docs/c/User_Interface/Layers.md#Layer "Layer") bounds.

A typical touch interaction starts with a single `TouchEvent_Touchdown`, followed by zero or more `TouchEvent_PositionUpdate` events as the finger moves, and ends with a single `TouchEvent_Liftoff`. Apps that want to track gestures (taps, drags, swipes) can do this by hand - store the touchdown position, watch the position updates, and decide what happened on lift-off - but in most cases the built-in [gesture recognizers](#gesture-recognizers) below do that work already.

Each [`TouchEvent`](https://developer.repebble.com/docs/c/Foundation/Event_Service/TouchService.md#TouchEvent "TouchEvent") also carries a `non_navigational` flag. It is set on touches that arrive while no _interaction session_ is active (see [Touch Navigation](#touch-navigation) below) — the user is touching the screen without having woken the watch first. Raw subscribers still receive these events and can decide for themselves whether to honor them.

## Touch Navigation

Since firmware 4.32, the system can translate touches into the button-based navigation model: tapping and swiping in a [`MenuLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md "MenuLayer") scrolls it and activates rows, taps on an [`ActionBarLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ActionBarLayer.md "ActionBarLayer") are zoned into up/select/down button events, and [`ActionMenu`](https://developer.repebble.com/docs/c/User_Interface/Window/ActionMenu.md "ActionMenu") items activate on tap. As of firmware 4.33 this _touch navigation_ is enabled by default, and it is gated on an **interaction session** : the user must press a button or wake the watch with a gesture before touches navigate. This prevents accidental navigation from brushing against an idle watchface.

Watchapps are **opted out** of touch navigation by default, so existing apps behave exactly as before. To let the system drive your menus and scroll views by touch, opt in once at startup:

```
app\_touch\_navigation\_enable(true);
```

With touch navigation enabled for the app, taps and swipes that don't hit a touch-aware widget are bridged to button click events, so a plain `click_config_provider` keeps working without any touch-specific code.

If one particular [`Window`](https://developer.repebble.com/docs/c/User_Interface/Window.md "Window") handles its own touch input - with a raw subscription or with recognizers - take it out of the touch bridge so the system doesn't consume its touches:

```
window\_set\_touch\_bridge\_disabled(window, true);
```

## Gesture Recognizers

Rather than tracking touchdown/move/lift-off sequences by hand, apps can create _recognizers_ that watch the touch stream for one specific gesture and report progress through a callback. Three recognizers are available:

| Recognizer | Constructor | Recognizes |
| --- | --- | --- |
| Tap | [`tap_recognizer_create()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#tap_recognizer_create "tap\_recognizer\_create") | A single tap. Read the location with [`tap_recognizer_get_tap_point()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#tap_recognizer_get_tap_point "tap\_recognizer\_get\_tap\_point"). |
| Pan | [`pan_recognizer_create()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_create "pan\_recognizer\_create") | A drag locked to one axis ([`PanAxis_Horizontal`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#PanAxis_Horizontal "PanAxis\_Horizontal") or [`PanAxis_Vertical`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#PanAxis_Vertical "PanAxis\_Vertical")). Read movement with [`pan_recognizer_get_total_delta()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_get_total_delta "pan\_recognizer\_get\_total\_delta"), [`pan_recognizer_get_delta_since_start()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_get_delta_since_start "pan\_recognizer\_get\_delta\_since\_start"), [`pan_recognizer_get_delta_since_prev()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_get_delta_since_prev "pan\_recognizer\_get\_delta\_since\_prev") and [`pan_recognizer_get_velocity()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#pan_recognizer_get_velocity "pan\_recognizer\_get\_velocity"). |
| Swipe | [`swipe_recognizer_create()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#swipe_recognizer_create "swipe\_recognizer\_create") | A fast, straight flick in one of the directions in the given [`SwipeDirection`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#SwipeDirection "SwipeDirection") mask. Read the result with [`swipe_recognizer_get_direction()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#swipe_recognizer_get_direction "swipe\_recognizer\_get\_direction") and [`swipe_recognizer_get_velocity()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#swipe_recognizer_get_velocity "swipe\_recognizer\_get\_velocity"). |

Each recognizer reports [`RecognizerEvent_Started`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerEvent_Started "RecognizerEvent\_Started"), [`RecognizerEvent_Updated`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerEvent_Updated "RecognizerEvent\_Updated"), [`RecognizerEvent_Completed`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerEvent_Completed "RecognizerEvent\_Completed") and [`RecognizerEvent_Cancelled`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerEvent_Cancelled "RecognizerEvent\_Cancelled") events to its [`RecognizerEventCb`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#RecognizerEventCb "RecognizerEventCb").

Recognizers are attached to a window with [`window_attach_recognizer()`](https://developer.repebble.com/docs/c/User_Interface/Window.md#window_attach_recognizer "window\_attach\_recognizer"). The window takes ownership and destroys attached recognizers when it unloads, so in the common case there is nothing to clean up manually ([`recognizer_destroy()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#recognizer_destroy "recognizer\_destroy") exists for recognizers that were never attached). Remember to also disable the window's touch bridge, or the system will consume the touches before your recognizers see them.

This example scrolls a custom [`ScrollLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/ScrollLayer.md "ScrollLayer") by finger — something a bare `ScrollLayer` does not do on its own:

```
static ScrollLayer \*s\_scroll;static int16\_t s\_base;// content offset committed on Completestatic void pan\_handler(const Recognizer \*recognizer, RecognizerEvent event) {switch (event) {case RecognizerEvent\_Updated: {// delta\_since\_start is (0, 0) at Start, so the content does not jumpGPoint d = pan\_recognizer\_get\_delta\_since\_start(recognizer);scroll\_layer\_set\_content\_offset(s\_scroll, GPoint(0, s\_base + d.y), false);break;}case RecognizerEvent\_Completed:// Commit the new offsets\_base = scroll\_layer\_get\_content\_offset(s\_scroll).y;break;case RecognizerEvent\_Cancelled:// Roll back to the last committed offsetscroll\_layer\_set\_content\_offset(s\_scroll, GPoint(0, s\_base), true);break;default:break;}}static void main\_window\_load(Window \*window) {Layer \*root = window\_get\_root\_layer(window);s\_scroll = scroll\_layer\_create(layer\_get\_bounds(root));scroll\_layer\_set\_content\_size(s\_scroll,GSize(layer\_get\_bounds(root).size.w, total\_content\_height));// Add custom row layers as children of s\_scroll herelayer\_add\_child(root, scroll\_layer\_get\_layer(s\_scroll));// Handle this window's touches ourselves instead of the system bridgewindow\_set\_touch\_bridge\_disabled(window, true);// The window owns the recognizer and destroys it when the window unloadsRecognizer \*pan = pan\_recognizer\_create(pan\_handler, NULL, PanAxis\_Vertical);window\_attach\_recognizer(window, pan);}
```

When several recognizers watch the same window, they are evaluated exclusively by default: the first one to recognize its gesture wins. Use [`recognizer_set_simultaneous_with()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#recognizer_set_simultaneous_with "recognizer\_set\_simultaneous\_with") to allow two recognizers to evaluate at the same time, or [`recognizer_set_fail_after()`](https://developer.repebble.com/docs/c/User_Interface/Gesture_Recognizers.md#recognizer_set_fail_after "recognizer\_set\_fail\_after") to hold one back until another has failed (e.g. only treat a touch as a tap once the swipe recognizer has given up).

Note that on touch hardware a [`MenuLayer`](https://developer.repebble.com/docs/c/User_Interface/Layers/MenuLayer.md "MenuLayer") already scrolls and activates by touch when the app has opted in to touch navigation. Recognizers are for interactions the built-in widgets don't provide, such as custom menus, drags, or acting on raw taps and swipes.

## Backlight Behavior

Each touch event triggers the backlight the same way a wrist flick or button press would - the light flashes on for the system auto-off interval and then fades out. This keeps the screen lit naturally while the user is actively interacting, without keeping the backlight pinned on between taps. There is no need to call the [Light API](https://developer.repebble.com/guides/events-and-services/light.md) manually to achieve this; subsequent touches will re-trigger the backlight on their own.

## Battery Considerations

The touch sensor is an active component and draws power continuously while enabled. Subscribe to the touch service only while the app's UI actually needs touch input, and unsubscribe as soon as it doesn't - for example, in the window `disappear` handler, or when navigating to a screen that uses buttons instead.
