GO FURTHER, FASTER: Try the Swift Career Accelerator today! >>

TabView Not Updating Correctly

Forums > SwiftUI

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()
    }
}

   

So I removed the NavigationStack on SettingView. This solved the problem and all works well. I will have to see how this effects my other views but for now I am going to say that NavigationStack inside a TabView may be a bad thing!

Anyone have an idea how to get NavStack working because I want the title to be displayed.

   

At a quick glance, it looks to me like you are observing the @State var "setting" to toggle the tabs, but you are changing settings[0].showBookTab, etc.

NavigationStack definately works with tabs. Every tab needs it's own navigationStack at the top level of a tab.

   

Hacking with Swift is sponsored by Alex.

SPONSORED Alex is the iOS & Mac developer’s ultimate AI assistant. It integrates with Xcode, offering a best-in-class Swift coding agent. Generate modern SwiftUI from images. Fast-apply suggestions from Claude 3.5 Sonnet, o3-mini, and DeepSeek R1. Autofix Swift 6 errors and warnings. And so much more. Start your 7-day free trial today!

Try for free!

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.