iOS: give the menu reveal tap a surface that works on iOS 27

The reveal tap moves onto a clear SwiftUI shape overlaid on the game
view, and the dynamic input zones report their classified taps through
a notification the game screen listens for.

The old gesture sat on the Metal render view, which iOS 27 makes non
interactive so the SwiftUI overlays own touch. On that OS the tap
could never fire, and once the controller poll was gone there was no
path left to bring a hidden menu button back. The zones matter for the
same reason: with dynamic thumbsticks or swipe camera on they tile the
whole landscape screen and eat every tap before it reaches anything
below.
This commit is contained in:
J1coding
2026-08-06 12:43:48 +02:00
committed by Jeen
parent 2697dfb97d
commit d0761f7285
3 changed files with 23 additions and 1 deletions
@@ -118,6 +118,8 @@ struct DynamicThumbstickView: View {
var onInteractionActivity: () -> Void = {}
var onInteractionTap: () -> Void = {}
var onInteractionEnded: () -> Void = {}
// Fires on every classified tap, unlike onInteractionTap which needs tap actions on.
var onAnyTap: () -> Void = {}
@State private var origin = CGPoint.zero
@State private var dragOffset = CGSize.zero
@@ -194,6 +196,7 @@ struct DynamicThumbstickView: View {
let duration = value.time.timeIntervalSince(gestureStartedAt ?? value.time)
let travel = max(maximumTravel, hypot(value.translation.width, value.translation.height))
if duration <= maximumTapDuration && travel <= tapTravelTolerance {
onAnyTap()
if tapActionsEnabled {
onInteractionTap()
} else {
@@ -356,6 +356,7 @@ struct GameScreenView: View {
.accessibilityLabel("Game display")
.accessibilityAddTraits(.isImage)
.accessibilityHint("VoiceOver image recognition can read on-screen text.")
.overlay { menuRevealTapCatcher }
AccessibilityHUDMirror()
if effectiveVirtualPadVisible {
VirtualControllerView(
@@ -390,6 +391,7 @@ struct GameScreenView: View {
.accessibilityHint("VoiceOver image recognition can read on-screen text.")
.overlay {
ZStack {
menuRevealTapCatcher
AccessibilityHUDMirror()
dynamicCrosshairOverlay
}
@@ -594,6 +596,9 @@ struct GameScreenView: View {
padRebuildToken &+= 1
overlayRoute = .paused
}
.onReceive(NotificationCenter.default.publisher(for: gameplaySurfaceTapNotification)) { _ in
revealMenuButtonBriefly()
}
.onReceive(NotificationCenter.default.publisher(for: retroAchievementsToastNotification)) { _ in
consumePendingRetroAchievementsToast()
}
@@ -793,6 +798,14 @@ struct GameScreenView: View {
.gameplayLaunchChrome(visible: appState.gameplayLaunchControlsVisible)
}
// The render view is non-interactive on iOS 27, so the reveal tap needs a SwiftUI surface.
private var menuRevealTapCatcher: some View {
Color.clear
.contentShape(Rectangle())
.onTapGesture { revealMenuButtonBriefly() }
.accessibilityHidden(true)
}
@ViewBuilder
private var menuButtonLabel: some View {
// Always-rendered SF Symbol mark. A loose PNG was previously loaded here, but
@@ -4,6 +4,10 @@
import SwiftUI
import UIKit
// Posted when a dynamic input zone classifies a touch as a tap, so the game
// screen can reveal a hidden menu button the zones would otherwise swallow.
let gameplaySurfaceTapNotification = Notification.Name("ARMSX2iOSGameplaySurfaceTap")
// Lazily-created haptic generators reused for button presses and explicitly releasable
// when the gameplay UI is removed.
@MainActor
@@ -567,6 +571,7 @@ struct VirtualControllerView: View {
}
},
onTap: {
NotificationCenter.default.post(name: gameplaySurfaceTapNotification, object: nil)
if dynamicSettings.rightThumbstickActionsEnabled {
activeTouchActionSession.right.interactionTapped()
}
@@ -644,7 +649,8 @@ struct VirtualControllerView: View {
onInteractionBegan: { actionController.interactionBegan() },
onInteractionActivity: { actionController.interactionActivity() },
onInteractionTap: { actionController.interactionTapped() },
onInteractionEnded: { actionController.interactionEnded() }
onInteractionEnded: { actionController.interactionEnded() },
onAnyTap: { NotificationCenter.default.post(name: gameplaySurfaceTapNotification, object: nil) }
)
}