Skip to content

Pan gesture: document that TotalX/TotalY semantics differ between iOS (cumulative) and Android (incremental) #3251

@PureWeen

Description

@PureWeen

Summary

The pan gesture documentation describes TotalX and TotalY properties without noting that their meaning differs between platforms. On iOS, these values are cumulative (relative to where the gesture started). On Android, they are incremental (relative to the previous event). This silent behavioral difference causes pan gesture implementations that work correctly on one platform to behave incorrectly on the other.

Why it matters

A developer testing on iOS will write code like:

pan.PanUpdated += (s, e) =>
{
    if (e.StatusType == GestureStatus.Running)
    {
        image.TranslationX = e.TotalX;  // works on iOS — cumulative from start
        image.TranslationY = e.TotalY;
    }
};

This code produces smooth, correct movement on iOS. On Android, TotalX/TotalY represent the delta since the last event, so directly assigning them causes the view to jump to a tiny offset each frame instead of tracking the full pan distance — a clearly broken UX that only manifests on Android.

The inverse is also true: code written for Android (accumulating deltas manually) will double-count on iOS.

What should be documented

Add a platform behavior note to docs/fundamentals/gestures/pan.md explaining the coordinate difference and providing a normalized pattern:

Platform TotalX / TotalY semantics
iOS Cumulative offset from the gesture start position
Android Incremental offset from the previous event
Windows Cumulative offset from the gesture start position

To write cross-platform pan code, track the previous translation and accumulate deltas on Android, or use the cumulative values directly on iOS/Windows:

double _startTranslationX, _startTranslationY;

pan.PanUpdated += (s, e) =>
{
    switch (e.StatusType)
    {
        case GestureStatus.Started:
            _startTranslationX = image.TranslationX;
            _startTranslationY = image.TranslationY;
            break;
        case GestureStatus.Running:
            image.TranslationX = _startTranslationX + e.TotalX;
            image.TranslationY = _startTranslationY + e.TotalY;
            break;
        case GestureStatus.Completed:
            _startTranslationX = image.TranslationX;
            _startTranslationY = image.TranslationY;
            break;
    }
};

This pattern works correctly on all platforms because it captures the starting translation and applies TotalX/TotalY as an offset from that baseline.

Suggested location

docs/fundamentals/gestures/pan.md — in the PanGestureRecognizer section, near the TotalX/TotalY property description

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions