toolbar(iOS): keep hide toolbar tip off the toolbar on iOS 26+

From iOS 26 the arrow edge passed to popoverTip is only a preference.
When the VM toolbar spans most of the screen width, the system attaches
the "Tap to hide/show toolbar" tip beside the chevron instead of below
it, covering the other toolbar buttons until the tip is dismissed. This
happened on iPhone on iOS 26.1 and on iPhone and iPad on iOS 27.

On iOS 26 and later, restrict the tip's arrow to the edge facing the
toolbar, using the top edge when the toolbar is docked at the top and
the bottom edge when it is docked at the bottom, so the tip always opens
toward the middle of the screen. iOS 17 through 25 keep the existing
behavior.

Assisted-by: Claude:claude-opus-5
This commit is contained in:
osy
2026-09-16 23:58:41 +02:00
parent 6fef92f151
commit 28a91b6385
+22 -3
View File
@@ -166,7 +166,7 @@ struct VMToolbarView: View {
Label("Hide", systemImage: isCollapsed ? nameOfHideIcon : nameOfShowIcon)
}.toolbarButtonStyle(horizontalSizeClass: horizontalSizeClass, verticalSizeClass: verticalSizeClass)
.animationUniqueID("hide", in: namespace)
.modifier(HideToolbarTipModifier(isCollapsed: $isCollapsed))
.modifier(HideToolbarTipModifier(isCollapsed: $isCollapsed, location: location))
.opacity(toolbarToggleOpacity)
.modifier(Shake(shake: shake))
.offset(dragOffset)
@@ -477,15 +477,28 @@ extension MenuStyle where Self == ToolbarMenuStyle {
private struct HideToolbarTipModifier: ViewModifier {
@Binding var isCollapsed: Bool
let location: ToolbarLocation
private let _hideToolbarTip: Any?
/// Keeps the tip on the side of the toolbar facing the middle of the screen.
///
/// From iOS 26 a single arrow edge is only a preference, and when the toolbar spans most of
/// the width the system attaches the tip beside the button instead, covering the toolbar.
private var arrowEdges: Edge.Set {
switch location {
case .topLeft, .topRight: return .top
case .bottomLeft, .bottomRight: return .bottom
}
}
@available(iOS 17, *)
private var hideToolbarTip: UTMTipHideToolbar {
_hideToolbarTip as! UTMTipHideToolbar
}
init(isCollapsed: Binding<Bool>) {
init(isCollapsed: Binding<Bool>, location: ToolbarLocation) {
_isCollapsed = isCollapsed
self.location = location
if #available(iOS 17, *) {
_hideToolbarTip = UTMTipHideToolbar()
} else {
@@ -495,7 +508,13 @@ private struct HideToolbarTipModifier: ViewModifier {
@ViewBuilder
func body(content: Content) -> some View {
if #available(iOS 17, *) {
if #available(iOS 26, *) {
content
.popoverTip(hideToolbarTip, arrowEdges: arrowEdges)
.onAppear {
UTMTipHideToolbar.didHideToolbar = isCollapsed
}
} else if #available(iOS 17, *) {
content
.popoverTip(hideToolbarTip, arrowEdge: .top)
.onAppear {