From d7ec351dd4f5b0497bdf6e94819a4b021581c941 Mon Sep 17 00:00:00 2001 From: kutesir Date: Wed, 27 May 2026 14:01:34 +0300 Subject: [PATCH] fix(layout): fix rotation crash and improve iPad split view iPadLayout was rendering TodayView without workoutVM, crashing with a fatal EnvironmentObject error on any device where hSizeClass goes .regular (iPad, iPhone Plus/Max in landscape). Rewrote to use proper NavigationSplitView with List(selection:) driving the detail column. Also caches calendarIcon as a static func + @State instead of recomputing UIGraphicsImageRenderer on every body evaluation. --- KisaniCal/ContentView.swift | 53 +++++++++++++------------------------ 1 file changed, 18 insertions(+), 35 deletions(-) diff --git a/KisaniCal/ContentView.swift b/KisaniCal/ContentView.swift index 545857c..ce5dc78 100644 --- a/KisaniCal/ContentView.swift +++ b/KisaniCal/ContentView.swift @@ -11,8 +11,10 @@ struct ContentView: View { @AppStorage("hasOnboarded") private var hasOnboarded = false @State private var showOnboarding = false @State private var selectedTab = 0 + @State private var iPadSelection: Int? = 0 + @State private var calIconImage: Image = ContentView.buildCalIcon() - private var calendarIcon: Image { + private static func buildCalIcon() -> Image { let day = Calendar.current.component(.day, from: Date()) let size = CGSize(width: 26, height: 26) let renderer = UIGraphicsImageRenderer(size: size) @@ -21,14 +23,12 @@ struct ContentView: View { let lw: CGFloat = 1.5 let cr: CGFloat = 4.5 - // Outer rounded rectangle body let body = CGRect(x: lw / 2, y: 3, width: size.width - lw, height: size.height - 3 - lw / 2) let bodyPath = UIBezierPath(roundedRect: body, cornerRadius: cr) color.setStroke() bodyPath.lineWidth = lw bodyPath.stroke() - // Header divider line let divY = body.minY + 7.5 let divPath = UIBezierPath() divPath.move(to: CGPoint(x: body.minX + lw / 2, y: divY)) @@ -37,13 +37,11 @@ struct ContentView: View { divPath.lineWidth = 1 divPath.stroke() - // Ring bumps at top color.setFill() for rx in [size.width * 0.3, size.width * 0.7] { UIBezierPath(roundedRect: CGRect(x: rx - 1.5, y: 0.5, width: 3, height: 5.5), cornerRadius: 1.5).fill() } - // Date number centred in body below header let s = "\(day)" let font = UIFont.systemFont(ofSize: floor(size.height * 0.38), weight: .bold) let attrs: [NSAttributedString.Key: Any] = [.font: font, .foregroundColor: color] @@ -133,7 +131,7 @@ struct ContentView: View { KisaniTabBar( selection: $selectedTab, - calIcon: calendarIcon, + calIcon: calIconImage, showMatrix: showMatrixTab, showWorkout: showWorkoutTab ) @@ -146,42 +144,27 @@ struct ContentView: View { // MARK: - iPad @ViewBuilder private func iPadLayout() -> some View { - NavigationSplitView(columnVisibility: .constant(.all)) { - List { - iPadSidebarItem("Tasks", icon: "checkmark", tag: 0) - iPadSidebarItem("Calendar", icon: "calendar", tag: 1) - if showMatrixTab { - iPadSidebarItem("Matrix", icon: "square.grid.2x2", tag: 2) - } - if showWorkoutTab { - iPadSidebarItem("Workout", icon: "dumbbell", tag: 3) - } - iPadSidebarItem("Settings", icon: "gearshape", tag: 4) + NavigationSplitView { + List(selection: $iPadSelection) { + Label("Tasks", systemImage: "checkmark").tag(0) + Label("Calendar", systemImage: "calendar").tag(1) + if showMatrixTab { Label("Matrix", systemImage: "square.grid.2x2").tag(2) } + if showWorkoutTab { Label("Workout", systemImage: "dumbbell").tag(3) } + Label("Settings", systemImage: "gearshape").tag(4) } .navigationTitle("Kisani Cal") .listStyle(.sidebar) } detail: { - TodayView().environmentObject(taskVM) + switch iPadSelection ?? 0 { + case 1: CalendarView().environmentObject(taskVM).environmentObject(workoutVM) + case 2: MatrixView().environmentObject(taskVM) + case 3: WorkoutView().environmentObject(workoutVM) + case 4: SettingsView().environmentObject(workoutVM) + default: TodayView().environmentObject(taskVM).environmentObject(workoutVM) + } } .tint(AppColors.accent) } - - private func iPadSidebarItem(_ label: String, icon: String, tag: Int) -> some View { - NavigationLink(destination: iPadDetail(tag: tag)) { - Label(label, systemImage: icon) - } - } - - @ViewBuilder - private func iPadDetail(tag: Int) -> some View { - switch tag { - case 0: TodayView().environmentObject(taskVM).environmentObject(workoutVM) - case 1: CalendarView().environmentObject(taskVM).environmentObject(workoutVM) - case 2: MatrixView().environmentObject(taskVM) - case 3: WorkoutView().environmentObject(workoutVM) - default: SettingsView().environmentObject(workoutVM) - } - } } // MARK: - Custom Tab Bar