fyne-io/fyne · error

out of touchIDs

Error message

out of touchIDs

What it means

iOS driver of Fyne's mobile/app package. It maps each active UITouch pointer to a slot in a fixed-size table, var touchIDs [11]uintptr in darwin_ios.go. sendEvent looks up the incoming UITouch pointer, else takes the first zero slot; if all 11 slots hold non-zero pointers it panics with 'out of touchIDs'. The whole table is cleared whenever any touch TypeEnd arrives because UITouch pointers are unique per multi-touch event (Fyne issue #2407).

Source

Thrown at internal/driver/mobile/app/darwin_ios.go:181

//export sendTouch
func sendTouch(cTouch, cTouchType uintptr, x, y float32) {
	id := -1
	for i, val := range touchIDs {
		if val == cTouch {
			id = i
			break
		}
	}
	if id == -1 {
		for i, val := range touchIDs {
			if val == 0 {
				touchIDs[i] = cTouch
				id = i
				break
			}
		}
		if id == -1 {
			panic("out of touchIDs")
		}
	}

	t := touch.Type(cTouchType)
	if t == touch.TypeEnd {
		// Clear all touchIDs when touch ends. The UITouch pointers are unique
		// at every multi-touch event. See:
		// https://github.com/fyne-io/fyne/issues/2407
		// https://developer.apple.com/documentation/uikit/touches_presses_and_gestures?language=objc
		for idx := range touchIDs {
			touchIDs[idx] = 0
		}
	}

	theApp.events.In() <- touch.Event{
		X:        x,
		Y:        y,
		Sequence: touch.Sequence(id),

View on GitHub (pinned to 8860ee95c3)

Solutions

  1. Keep simultaneously tracked touches at 10 or fewer for headroom under the 11-slot table
  2. Ensure every synthesized or intercepted touch eventually delivers a touch.TypeEnd so the table clears
  3. Upgrade Fyne and check release notes for darwin_ios touch handling changes beyond the #2407 fix
  4. If you fork/vendor the driver, enlarge touchIDs or drop unknown touches (log and return) instead of panicking

Example fix

// before (test harness): 12 begin events, no ends -> panic: out of touchIDs
for i := 0; i < 12; i++ {
	injectTouch(uintptr(i), touch.TypeBegin)
}

// after: stay under 11 slots and always end touches
const maxTouches = 10
for i := 0; i < maxTouches; i++ {
	injectTouch(uintptr(i), touch.TypeBegin)
}
for i := 0; i < maxTouches; i++ {
	injectTouch(uintptr(i), touch.TypeEnd)
}
Defensive patterns

Strategy: try-catch

Try / catch

// The panic fires on the iOS event thread inside the driver, so a normal
// recover cannot save the process; wrap app startup so crashes at least
// produce a labeled report before the app dies.
func runGuarded(a fyne.App) {
	defer func() {
		if r := recover(); r != nil {
			log.Printf("driver panic (likely touchIDs exhaustion): %v", r)
			panic(r) // re-panic to keep the crash report
		}
	}()
	a.Run()
}

Prevention

When it happens

Trigger: A 12th simultaneous touch begins while 11 slots are live; or a new UITouch pointer arrives with no free slot because no TypeEnd event has cleared the table yet (dropped/cancelled touches, gesture recognizers swallowing events, automation that never sends TypeEnd).

Common situations: Multi-touch apps on iPad (hardware tracks more than 11 pointers); UI-automation and stress tests firing touch bursts without matching end events; custom gesture recognizers that cancel touches without forwarding them.

Related errors


AI-assisted analysis of fyne-io/fyne@8860ee95c3 (2026-08-15). Data as JSON: /api/errors/255f5f1f45f3cd5d. Report an issue: GitHub.