Skip to content

Commit cacbb95

Browse files
committed
Fix the tab rail flickering on narrow iPad windows
The rail is laid out beside the split view, so hiding it when the split collapsed handed that width back to the split, which then had room for its sidebar again, which showed the rail, which collapsed the split. Any window whose width sat within a rail of the split's collapse threshold flickered forever, an iPad mini in fullscreen among them. The split reports the same column visibility whether it collapsed on its own or because the sidebar was toggled, and at the same width, so the two can only be told apart by when they arrive: the rebound lands in the next layout pass. A collapse that soon after showing the rail is therefore taken as proof that the rail and the sidebar don't fit at this width, and the rail is left hidden until the window is resized. Later collapses are treated as deliberate and still hide the rail as before.
1 parent b4395d9 commit cacbb95

1 file changed

Lines changed: 27 additions & 3 deletions

File tree

ElementX/Sources/Application/Navigation/NavigationTabCoordinator.swift

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,11 @@ private struct NavigationTabCoordinatorView<Tag: Hashable>: View {
317317
@State private var isFullScreen = true
318318
@State private var isRailVisible = true
319319
@State private var railBackgroundColor: Color = .compound.bgCanvasDefault
320+
@State private var containerWidth: CGFloat = 0
321+
/// The container width at which the rail and the split view's sidebar have been shown not to fit together.
322+
@State private var railBlockedWidth: CGFloat?
323+
/// When the rail was last shown, used to spot the split view collapsing as a direct result of it.
324+
@State private var railShownDate = Date.distantPast
320325

321326
var body: some View {
322327
tabView
@@ -385,8 +390,13 @@ private struct NavigationTabCoordinatorView<Tag: Hashable>: View {
385390
}
386391
}
387392
.onGeometryChange(for: CGSize.self) { geometry in
388-
geometry.size // We don't need the size, but it's a signal to re-compute.
389-
} action: { _ in
393+
geometry.size
394+
} action: { size in
395+
if size.width != containerWidth {
396+
containerWidth = size.width
397+
railBlockedWidth = nil // The rail and the sidebar may well fit at the new width.
398+
}
399+
390400
guard let newValue = window?.isFullScreen else { return }
391401

392402
if newValue != isFullScreen {
@@ -399,7 +409,21 @@ private struct NavigationTabCoordinatorView<Tag: Hashable>: View {
399409
// feedback loop that locks up the app while resizing.
400410
//
401411
// Use a stored value (instead of a computed value) that is updated on the next run loop to break the loop.
402-
DispatchQueue.main.async { isRailVisible = selectedTabSplitColumnVisibility != .detailOnly }
412+
DispatchQueue.main.async {
413+
if selectedTabSplitColumnVisibility == .detailOnly {
414+
// A collapse this soon after showing the rail was caused by the rail's own width, so
415+
// showing it again would only collapse the split view again, flickering forever. Note
416+
// that the two don't fit at this width and leave the rail hidden until that changes.
417+
if railShownDate.timeIntervalSinceNow > -0.5 {
418+
railBlockedWidth = containerWidth
419+
}
420+
421+
isRailVisible = false
422+
} else if !isRailVisible, railBlockedWidth != containerWidth {
423+
isRailVisible = true
424+
railShownDate = .now
425+
}
426+
}
403427
}
404428
}
405429

0 commit comments

Comments
 (0)