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
- Keep simultaneously tracked touches at 10 or fewer for headroom under the 11-slot table
- Ensure every synthesized or intercepted touch eventually delivers a touch.TypeEnd so the table clears
- Upgrade Fyne and check release notes for darwin_ios touch handling changes beyond the #2407 fix
- 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
- Cap simultaneous tracked touches in app logic at 10 (table holds 11) so the driver never runs out of slots
- In UI automation, always pair every TypeBegin with a TypeEnd (any end event clears the whole table)
- Forward cancelled touches from custom gesture recognizers so end events are not lost
- Run multi-touch stress tests on real hardware before shipping iPad apps
- Track Fyne updates to darwin_ios.go and upgrade; the table-clearing behavior may change
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
- unknown GOARCH: %q
- malformed env var %q from input
- unimplemented; GOOS/CGO combination not supported
- unhandled token type: %T %+v
- encountered empty values slice
AI-assisted analysis of fyne-io/fyne@8860ee95c3 (2026-08-15).
Data as JSON: /api/errors/255f5f1f45f3cd5d.
Report an issue: GitHub.