fix(ios): replace 2001-page TabView with 3-page recycling carousel

Eliminates the eager instantiation of 2001 view bodies by keeping only
three pages (previous, current, next) alive at all times. After each
swipe settles, dayOffset is shifted and tabSelection is silently reset
to the middle page, preserving the native paging animation.
This commit is contained in:
simon.franken
2026-02-23 10:09:51 +01:00
parent b971569983
commit 544b86c948

View File

@@ -2,11 +2,13 @@ import SwiftUI
struct TimeEntriesView: View {
@StateObject private var viewModel = TimeEntriesViewModel()
@State private var selectedDate: Date = Calendar.current.startOfDay(for: Date())
// For infinite paging, we use an offset from 'today'
@State private var dayOffset: Int = 0
// dayOffset is the source of truth: 0 = today, -1 = yesterday, etc.
@State private var dayOffset: Int = 0
// tabSelection is always snapped back to 1 (middle) after each swipe.
// Pages are: 0 = dayOffset-1, 1 = dayOffset, 2 = dayOffset+1
@State private var tabSelection: Int = 1
@State private var showFilterSheet = false
@State private var showAddEntry = false
@State private var entryToEdit: TimeEntry?
@@ -101,20 +103,37 @@ struct TimeEntriesView: View {
// MARK: - Main content
private var mainContent: some View {
// We use a wide range of offsets to simulate infinite paging.
// -1000 days is roughly 2.7 years in the past.
// 1000 days is roughly 2.7 years in the future.
TabView(selection: $dayOffset) {
ForEach(-1000...1000, id: \.self) { offset in
let dayForPage = Calendar.current.date(byAdding: .day, value: offset, to: Calendar.current.startOfDay(for: Date())) ?? Date()
// Only 3 pages exist at any time: previous, current, next.
// After each swipe settles, we reset tabSelection to 1 and shift
// dayOffset, so the carousel appears infinite while staying cheap.
TabView(selection: $tabSelection) {
ForEach(0..<3, id: \.self) { page in
let offset = dayOffset + (page - 1)
let day = Calendar.current.date(
byAdding: .day,
value: offset,
to: Calendar.current.startOfDay(for: Date())
) ?? Date()
ScrollView {
dayEntriesSection(for: dayForPage)
dayEntriesSection(for: day)
}
.tag(offset) // Important: tag must match the selection type (Int)
.tag(page)
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
.onChange(of: tabSelection) { _, newPage in
guard newPage != 1 else { return }
// Shift the logical day offset by how many pages we moved.
dayOffset += newPage - 1
// Snap back to the middle page without animation so the
// surrounding pages are refreshed invisibly.
var tx = Transaction()
tx.disablesAnimations = true
withTransaction(tx) {
tabSelection = 1
}
}
.tabViewStyle(.page(indexDisplayMode: .never)) // Native swipe gesture
}
// MARK: - Day entries section