From 8d7cc2ed58f3d0160bec238083905364a87f2168 Mon Sep 17 00:00:00 2001 From: Hariharan Mudaliar Date: Sun, 1 Mar 2026 02:01:02 +0530 Subject: [PATCH] Fix: Janky Animation --- DynamicIsland/ContentView.swift | 22 +++++++++++++++++-- .../DynamicIslandViewCoordinator.swift | 9 ++++++++ .../components/Tabs/TabSelectionView.swift | 15 +++++++------ 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/DynamicIsland/ContentView.swift b/DynamicIsland/ContentView.swift index bc48153..44ca54b 100644 --- a/DynamicIsland/ContentView.swift +++ b/DynamicIsland/ContentView.swift @@ -184,6 +184,22 @@ struct ContentView: View { private let musicControlPauseGrace: TimeInterval = 5 private let musicControlResumeDelay: TimeInterval = 0.24 + // MARK: - Tab switch direction for smooth transitions + + private var tabSwitchTransition: AnyTransition { + if coordinator.tabSwitchForward { + return .asymmetric( + insertion: .move(edge: .trailing).combined(with: .opacity), + removal: .move(edge: .leading).combined(with: .opacity) + ) + } else { + return .asymmetric( + insertion: .move(edge: .leading).combined(with: .opacity), + removal: .move(edge: .trailing).combined(with: .opacity) + ) + } + } + private var standardMediaControlsActive: Bool { showStandardMediaControls && !enableMinimalisticUI } @@ -399,7 +415,6 @@ struct ContentView: View { alignment: .top ) .frame(maxHeight: .infinity, alignment: .top) - .animation(nil, value: coordinator.currentView) .environmentObject(privacyManager) .onChange(of: dynamicNotchSize) { oldSize, newSize in guard oldSize != newSize else { return } @@ -755,13 +770,16 @@ struct ContentView: View { } } } - .id(coordinator.currentView) // Force SwiftUI to treat each view as unique + .id(coordinator.currentView) + .transition(tabSwitchTransition) } } + .clipped() .zIndex(1) .allowsHitTesting(vm.notchState == .open) .blur(radius: abs(gestureProgress) > 0.3 ? min(abs(gestureProgress), 8) : 0) .opacity(abs(gestureProgress) > 0.3 ? min(abs(gestureProgress * 2), 0.8) : 1) + .animation(.smooth(duration: 0.3), value: coordinator.currentView) } } diff --git a/DynamicIsland/DynamicIslandViewCoordinator.swift b/DynamicIsland/DynamicIslandViewCoordinator.swift index efffb39..c29ce36 100644 --- a/DynamicIsland/DynamicIslandViewCoordinator.swift +++ b/DynamicIsland/DynamicIslandViewCoordinator.swift @@ -102,12 +102,21 @@ class DynamicIslandViewCoordinator: ObservableObject { static let shared = DynamicIslandViewCoordinator() private var cancellables = Set() + private static let tabOrder: [NotchViews] = [.home, .shelf, .timer, .stats, .colorPicker, .notes, .clipboard, .extensionExperience] + + /// Direction of the most recent tab switch (true = forward/right, false = backward/left) + @Published var tabSwitchForward: Bool = true + @Published var currentView: NotchViews = .home { didSet { if Defaults[.enableMinimalisticUI] && currentView != .home { currentView = .home return } + // Track direction before SwiftUI re-renders + let oldIdx = Self.tabOrder.firstIndex(of: oldValue) ?? 0 + let newIdx = Self.tabOrder.firstIndex(of: currentView) ?? 0 + tabSwitchForward = newIdx >= oldIdx handleStatsTabTransition(from: oldValue, to: currentView) } } diff --git a/DynamicIsland/components/Tabs/TabSelectionView.swift b/DynamicIsland/components/Tabs/TabSelectionView.swift index 1e80585..c75ab59 100644 --- a/DynamicIsland/components/Tabs/TabSelectionView.swift +++ b/DynamicIsland/components/Tabs/TabSelectionView.swift @@ -99,9 +99,6 @@ struct TabSelectionView: View { ) } } - DispatchQueue.main.async { - ensureValidSelection(with: tabsArray) - } return tabsArray } var body: some View { @@ -110,10 +107,12 @@ struct TabSelectionView: View { let isSelected = isSelected(tab) let activeAccent = tab.accentColor ?? .white TabButton(label: tab.label, icon: tab.icon, selected: isSelected) { - if tab.view == .extensionExperience { - coordinator.selectedExtensionExperienceID = tab.experienceID + withAnimation(.smooth(duration: 0.3)) { + if tab.view == .extensionExperience { + coordinator.selectedExtensionExperienceID = tab.experienceID + } + coordinator.currentView = tab.view } - coordinator.currentView = tab.view } .frame(height: 26) .foregroundStyle(isSelected ? activeAccent : .gray) @@ -132,8 +131,10 @@ struct TabSelectionView: View { } } } - .animation(.smooth, value: coordinator.currentView) .clipShape(Capsule()) + .onAppear { + ensureValidSelection(with: tabs) + } } private var extensionTabsEnabled: Bool {