Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,48 @@
@Entry var tabViewHorizontalSizeClass: UserInterfaceSizeClass?
}

/// Tracks whether the tab rail should be shown alongside the split view's sidebar.
///
/// The rail is laid out beside the split view, so hiding it hands that width back to the split, which may
/// then have room for its sidebar again, which would show the rail, ad infinitum. 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 are told apart by when they arrive: the rebound lands in the next layout pass.
private struct TabRailVisibility {
/// The longest a collapse can follow the rail being shown and still be considered caused by it.
private static let reboundDuration: TimeInterval = 0.5

private(set) var isVisible = true

private var containerWidth: CGFloat = 0
/// The container width at which the rail and the sidebar have been shown not to fit together.
private var blockedWidth: CGFloat?
/// When the rail was last shown, used to spot the split view collapsing as a direct result of it.
private var shownDate = Date.distantPast

mutating func containerWidthChanged(to width: CGFloat) {
guard width != containerWidth else { return }

containerWidth = width
blockedWidth = nil // The rail and the sidebar may well fit at the new width.
}

mutating func splitVisibilityChanged(to visibility: NavigationSplitViewVisibility) {
if visibility == .detailOnly {
// A collapse this soon after showing the rail was caused by the rail's own width, so showing
// it again would only collapse the split view again, flickering forever. Note that the two
// don't fit at this width and leave the rail hidden until that changes.
if shownDate.timeIntervalSinceNow > -Self.reboundDuration {
blockedWidth = containerWidth
}

isVisible = false
} else if !isVisible, blockedWidth != containerWidth {
isVisible = true
shownDate = .now
}
}
}

private struct NavigationTabCoordinatorView<Tag: Hashable>: View {
@Environment(\.horizontalSizeClass) private var horizontalSizeClass

Expand All @@ -315,7 +357,7 @@
@State private var standardAppearance = UITabBarAppearance()
@State private var window: UIWindow?
@State private var isFullScreen = true
@State private var isRailVisible = true
@State private var railVisibility = TabRailVisibility()
@State private var railBackgroundColor: Color = .compound.bgCanvasDefault

var body: some View {
Expand Down Expand Up @@ -361,7 +403,7 @@

var tabRailLayout: some View {
HStack(spacing: 0) {
if isRailVisible {
if railVisibility.isVisible {
TabRailView(navigationTabCoordinator: navigationTabCoordinator, isFullScreen: isFullScreen)
.background(railBackgroundColor.ignoresSafeArea())
.animation(.easeInOut(duration: 0.4).disabledDuringTests(), value: railBackgroundColor)
Expand All @@ -385,8 +427,10 @@
}
}
.onGeometryChange(for: CGSize.self) { geometry in
geometry.size // We don't need the size, but it's a signal to re-compute.
} action: { _ in
geometry.size
} action: { size in
railVisibility.containerWidthChanged(to: size.width)

guard let newValue = window?.isFullScreen else { return }

if newValue != isFullScreen {
Expand All @@ -399,7 +443,9 @@
// feedback loop that locks up the app while resizing.
//
// Use a stored value (instead of a computed value) that is updated on the next run loop to break the loop.
DispatchQueue.main.async { isRailVisible = selectedTabSplitColumnVisibility != .detailOnly }
DispatchQueue.main.async {
railVisibility.splitVisibilityChanged(to: selectedTabSplitColumnVisibility)
}
}
}

Expand Down Expand Up @@ -458,7 +504,7 @@
}
.buttonStyle(TabButtonStyle(isSelected: isSelected))
.accessibilityAddTraits(isSelected ? .isSelected : [])
.badge(10) // TODO: Check if this works.

Check warning on line 507 in ElementX/Sources/Application/Navigation/NavigationTabCoordinator.swift

View workflow job for this annotation

GitHub Actions / Tests

Todo Violation: TODOs should be resolved (Check if this works.) (todo)
}
}
.padding(.leading, 8)
Expand Down
Loading