@@ -75,28 +75,33 @@ private extension BookingListView {
7575 }
7676
7777 var topTabView : some View {
78- HStack {
79- ForEach ( Array ( tabs. enumerated ( ) ) , id: \. element) { ( index, title) in
80- Button {
81- selectedTabIndex = index
82- } label: {
83- Text ( title)
84- . font ( . subheadline)
85- . foregroundStyle ( selectedTabIndex == index ? Color . accentColor : Color . primary)
86- }
87- . frame ( maxWidth: . infinity)
88- . padding ( . vertical, 12 )
89- . overlay {
90- VStack {
91- Spacer ( )
92- if selectedTabIndex == index {
93- Color . accentColor
94- . frame ( height: Layout . selectedTabIndicatorHeight)
78+ GeometryReader { geometry in
79+ HStack {
80+ ForEach ( Array ( tabs. enumerated ( ) ) , id: \. element) { ( index, title) in
81+ Button {
82+ withAnimation ( . easeInOut( duration: 0.3 ) ) {
83+ selectedTabIndex = index
9584 }
85+ } label: {
86+ Text ( title)
87+ . font ( . subheadline)
88+ . foregroundStyle ( selectedTabIndex == index ? Color . accentColor : Color . primary)
9689 }
90+ . frame ( maxWidth: . infinity)
91+ . padding ( . vertical, 12 )
9792 }
9893 }
94+ . overlay ( alignment: . bottom) {
95+ Color . accentColor
96+ . frame ( width: geometry. size. width / CGFloat( tabs. count) ,
97+ height: Layout . selectedTabIndicatorHeight)
98+ . offset ( x: tabIndicatorOffset ( containerWidth: geometry. size. width,
99+ tabCount: tabs. count,
100+ selectedIndex: selectedTabIndex) )
101+ . animation ( . easeInOut( duration: 0.3 ) , value: selectedTabIndex)
102+ }
99103 }
104+ . frame ( height: Layout . topTabBarHeight)
100105 . background ( Color ( . listForeground( modal: false ) ) )
101106 }
102107
@@ -165,10 +170,25 @@ private extension BookingListView {
165170 . padding ( . vertical, 4 )
166171 . background ( color. clipShape ( RoundedRectangle ( cornerRadius: 4 ) ) )
167172 }
173+
174+ /// SwiftUI's coordinate system places (0,0) at the center of the container, so we need to:
175+ /// 1. Calculate how far the selected tab is from the left edge
176+ /// 2. Adjust for the center-based coordinate system
177+ /// 3. Center the indicator within the selected tab
178+ ///
179+ func tabIndicatorOffset( containerWidth: CGFloat , tabCount: Int , selectedIndex: Int ) -> CGFloat {
180+ let tabWidth = containerWidth / CGFloat( tabCount)
181+ let distanceFromLeftEdge = tabWidth * CGFloat( selectedIndex)
182+ let adjustmentForCenterOrigin = containerWidth / 2
183+ let centerWithinTab = tabWidth / 2
184+
185+ return distanceFromLeftEdge - adjustmentForCenterOrigin + centerWithinTab
186+ }
168187}
169188private extension BookingListView {
170189 enum Layout {
171190 static let viewPadding : CGFloat = 16
191+ static let topTabBarHeight : CGFloat = 44
172192 static let selectedTabIndicatorHeight : CGFloat = 3.0
173193 static let defaultBadgeColor = Color ( uiColor: . init( light: . systemGray6, dark: . systemGray5) )
174194 }
0 commit comments