I've been looking at this for hours now. When my app loads, all tabs in MainView are visible (as they are supposed to be). When I tab to SettingsView, I am able to change my settings and they save correctly (I've purposefully limited the onChange events in this post). However, when I toggle one of the showAuthorTab, showBookTab, or showPuzzleTab, my SettingsView goes blank. The tabs are still at the bottom of the screen and the tab that was toggled off goes from the view (as it should). I can go to the other tabs without an issue. The only way to get the SettingsView to come back is to stop the app and restart it. When I do that, the state of everything is exactly how it should be based on the choices I made. Now, if I go back in and toggle the tab back on, it shows up. I can click on all the tabs except that one. When I click that one, the app crashes.
It seems as though the TabView is losing track of which tabs are visible. Can someone please help with this? It is driving me crazy and I am sure it is something very simple that I am missing.
import SwiftData
import SwiftUI
struct MainView: View {
// MARK: - PROPERTIES
@Query var settings: [Setting]
@State private var setting: Setting = Setting.samples[0]
// MARK: - VIEW
var body: some View {
TabView {
HomeView()
.tabItem { Label("Home", systemImage: "house.circle.fill") }
if setting.showAuthorTab {
AuthorView()
.tabItem { Label("Authors", systemImage: "pencil.and.scribble") }
}
if setting.showBookTab {
BookView()
.tabItem { Label("Books", systemImage: "books.vertical.fill") }
}
if setting.showPuzzleTab {
PuzzleView()
.tabItem { Label("Puzzles", systemImage: "puzzlepiece.fill") }
}
SettingsView()
.tabItem { Label("Settings", systemImage: "gearshape.circle.fill") }
}
.tint(Color(hex: setting.accentColor))
.onAppear {
if let firstSetting = settings.first {
setting = firstSetting
}
}
}
}
struct SettingsView: View {
// MARK: - PRIVATE ENUMERATION
enum SettingsFields: String {
case showAuthorTab, showBookTab, showPuzzleTab, allowNotification, medium, status, readingGoal, readingGoalStartDate, puzzleGoal, puzzleGoalStartDate, chosenAccentColor
}
// MARK: - PROPERTIES
@Environment(\.modelContext) private var context
@Query var settings: [Setting]
@State private var showAuthorTab: Bool = true
@State private var showBookTab: Bool = true
@State private var showPuzzleTab: Bool = true
@State private var allowNotifications: Bool = false
@State private var medium: String = Medium.paperback.rawValue
@State private var status: String = Status.onshelf.rawValue
@State private var readingGoal: Int = 10
@State private var readingGoalStartDate: Date = Date.now
@State private var puzzleGoal: Int = 10
@State private var puzzleGoalStartDate: Date = Date.now
@State private var chosenAccentColor: Color = Color.blue
// MARK: - VIEW
var body: some View {
NavigationStack {
Form {
Section {
CustomLabelAndToggleView(showTab: $showAuthorTab, image: "pencil.and.scribble", text: "Author Tab")
CustomLabelAndToggleView(showTab: $showBookTab, image: "books.vertical", text: "Book Tab")
CustomLabelAndToggleView(showTab: $showPuzzleTab, image: "puzzlepiece", text: "Puzzle Tab")
} header: {
Text("Choose which tabs to show")
}
Section {
CustomLabelAndToggleView(showTab: $allowNotifications, image: "megaphone", text: "Notifications")
} header: {
Text("Allow Notifications?")
}
Section {
Picker(selection: $medium) {
ForEach(Medium.allCases) { medium in
Text(medium.icon + " " + medium.rawValue).tag(medium.rawValue)
.foregroundStyle(.primary)
}
} label: {
HStack {
Image(systemName: "hand.thumbsup")
Text("Medium")
}
}
Picker(selection: $status) {
ForEach(Status.allCases) { status in
Text(status.icon + " " + status.rawValue).tag(status.rawValue)
.foregroundStyle(.primary)
}
} label: {
HStack {
Image(systemName: "tray.2")
Text("Status")
}
}
} header: {
Text("Boozzle Defaults")
}
Section {
LabeledContent {
TextField("Reading Goal", value: $readingGoal, format: .number)
.keyboardType(.numberPad)
.multilineTextAlignment(.trailing)
.textFieldStyle(.roundedBorder)
} label: {
HStack {
Image(systemName: "book.circle")
Text("Reading Goal")
Spacer()
}
}
LabeledContent {
DatePicker("", selection: $readingGoalStartDate, displayedComponents: [.date])
} label: {
HStack {
Image(systemName: "calendar")
Text("Start Date")
}
}
LabeledContent {
TextField("Puzzle Goal", value: $puzzleGoal, format: .number)
.keyboardType(.numberPad)
.multilineTextAlignment(.trailing)
.textFieldStyle(.roundedBorder)
} label: {
HStack {
Image(systemName: "book.circle")
Text("Puzzle Goal")
Spacer()
}
}
LabeledContent {
DatePicker("", selection: $puzzleGoalStartDate, displayedComponents: [.date])
} label: {
HStack {
Image(systemName: "calendar")
Text("Start Date")
}
}
} header: {
Text("Goals")
}
Section {
LabeledContent {
ColorPicker("", selection: $chosenAccentColor)
} label: {
HStack {
Image(systemName: "swatchpalette")
Text("Accent Color")
}
}
} header: {
Text("Accent Color")
}
}
.navigationTitle("Settings")
.onAppear {
if let setting = settings.first {
showAuthorTab = setting.showAuthorTab
showBookTab = setting.showBookTab
showPuzzleTab = setting.showPuzzleTab
allowNotifications = setting.allowNotifications
medium = setting.medium
status = setting.status
readingGoal = setting.readingGoal
readingGoalStartDate = setting.readingGoalStartDate
puzzleGoal = setting.puzzleGoal
puzzleGoalStartDate = setting.puzzleGoalStartDate
chosenAccentColor = Color(hex: setting.accentColor) ?? .blue
} else {
print("Settings are now missing")
}
}
.onChange(of: showAuthorTab) { _, _ in
save(.showAuthorTab)
}
.onChange(of: showBookTab) { _, _ in
save(.showBookTab)
}
.onChange(of: showPuzzleTab) { _, _ in
save(.showPuzzleTab)
}
}
}
// MARK: - METHODS
func save(_ setting: SettingsFields) {
switch setting {
case .showAuthorTab:
settings[0].showAuthorTab = showAuthorTab
case .showBookTab:
settings[0].showBookTab = showBookTab
case .showPuzzleTab:
settings[0].showPuzzleTab = showPuzzleTab
case .allowNotification:
settings[0].allowNotifications = allowNotifications
case .medium:
settings[0].medium = medium
case .status:
settings[0].status = status
case .readingGoal:
settings[0].readingGoal = readingGoal
case .readingGoalStartDate:
settings[0].readingGoalStartDate = readingGoalStartDate
case .puzzleGoal:
settings[0].puzzleGoal = puzzleGoal
case .puzzleGoalStartDate:
settings[0].puzzleGoalStartDate = puzzleGoalStartDate
case .chosenAccentColor:
settings[0].accentColor = chosenAccentColor.toHexString() ?? "#00FF00"
}
try? context.save()
}
}