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.
This commit is contained in:
@@ -11,8 +11,10 @@ struct ContentView: View {
|
|||||||
@AppStorage("hasOnboarded") private var hasOnboarded = false
|
@AppStorage("hasOnboarded") private var hasOnboarded = false
|
||||||
@State private var showOnboarding = false
|
@State private var showOnboarding = false
|
||||||
@State private var selectedTab = 0
|
@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 day = Calendar.current.component(.day, from: Date())
|
||||||
let size = CGSize(width: 26, height: 26)
|
let size = CGSize(width: 26, height: 26)
|
||||||
let renderer = UIGraphicsImageRenderer(size: size)
|
let renderer = UIGraphicsImageRenderer(size: size)
|
||||||
@@ -21,14 +23,12 @@ struct ContentView: View {
|
|||||||
let lw: CGFloat = 1.5
|
let lw: CGFloat = 1.5
|
||||||
let cr: CGFloat = 4.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 body = CGRect(x: lw / 2, y: 3, width: size.width - lw, height: size.height - 3 - lw / 2)
|
||||||
let bodyPath = UIBezierPath(roundedRect: body, cornerRadius: cr)
|
let bodyPath = UIBezierPath(roundedRect: body, cornerRadius: cr)
|
||||||
color.setStroke()
|
color.setStroke()
|
||||||
bodyPath.lineWidth = lw
|
bodyPath.lineWidth = lw
|
||||||
bodyPath.stroke()
|
bodyPath.stroke()
|
||||||
|
|
||||||
// Header divider line
|
|
||||||
let divY = body.minY + 7.5
|
let divY = body.minY + 7.5
|
||||||
let divPath = UIBezierPath()
|
let divPath = UIBezierPath()
|
||||||
divPath.move(to: CGPoint(x: body.minX + lw / 2, y: divY))
|
divPath.move(to: CGPoint(x: body.minX + lw / 2, y: divY))
|
||||||
@@ -37,13 +37,11 @@ struct ContentView: View {
|
|||||||
divPath.lineWidth = 1
|
divPath.lineWidth = 1
|
||||||
divPath.stroke()
|
divPath.stroke()
|
||||||
|
|
||||||
// Ring bumps at top
|
|
||||||
color.setFill()
|
color.setFill()
|
||||||
for rx in [size.width * 0.3, size.width * 0.7] {
|
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()
|
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 s = "\(day)"
|
||||||
let font = UIFont.systemFont(ofSize: floor(size.height * 0.38), weight: .bold)
|
let font = UIFont.systemFont(ofSize: floor(size.height * 0.38), weight: .bold)
|
||||||
let attrs: [NSAttributedString.Key: Any] = [.font: font, .foregroundColor: color]
|
let attrs: [NSAttributedString.Key: Any] = [.font: font, .foregroundColor: color]
|
||||||
@@ -133,7 +131,7 @@ struct ContentView: View {
|
|||||||
|
|
||||||
KisaniTabBar(
|
KisaniTabBar(
|
||||||
selection: $selectedTab,
|
selection: $selectedTab,
|
||||||
calIcon: calendarIcon,
|
calIcon: calIconImage,
|
||||||
showMatrix: showMatrixTab,
|
showMatrix: showMatrixTab,
|
||||||
showWorkout: showWorkoutTab
|
showWorkout: showWorkoutTab
|
||||||
)
|
)
|
||||||
@@ -146,42 +144,27 @@ struct ContentView: View {
|
|||||||
// MARK: - iPad
|
// MARK: - iPad
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func iPadLayout() -> some View {
|
private func iPadLayout() -> some View {
|
||||||
NavigationSplitView(columnVisibility: .constant(.all)) {
|
NavigationSplitView {
|
||||||
List {
|
List(selection: $iPadSelection) {
|
||||||
iPadSidebarItem("Tasks", icon: "checkmark", tag: 0)
|
Label("Tasks", systemImage: "checkmark").tag(0)
|
||||||
iPadSidebarItem("Calendar", icon: "calendar", tag: 1)
|
Label("Calendar", systemImage: "calendar").tag(1)
|
||||||
if showMatrixTab {
|
if showMatrixTab { Label("Matrix", systemImage: "square.grid.2x2").tag(2) }
|
||||||
iPadSidebarItem("Matrix", icon: "square.grid.2x2", tag: 2)
|
if showWorkoutTab { Label("Workout", systemImage: "dumbbell").tag(3) }
|
||||||
}
|
Label("Settings", systemImage: "gearshape").tag(4)
|
||||||
if showWorkoutTab {
|
|
||||||
iPadSidebarItem("Workout", icon: "dumbbell", tag: 3)
|
|
||||||
}
|
|
||||||
iPadSidebarItem("Settings", icon: "gearshape", tag: 4)
|
|
||||||
}
|
}
|
||||||
.navigationTitle("Kisani Cal")
|
.navigationTitle("Kisani Cal")
|
||||||
.listStyle(.sidebar)
|
.listStyle(.sidebar)
|
||||||
} detail: {
|
} 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)
|
.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
|
// MARK: - Custom Tab Bar
|
||||||
|
|||||||
Reference in New Issue
Block a user