I have something like this:
import SwiftUIstruct ContentView: View { @State private var showOnboarding = false @State private var dismissOnboarding = false @StateObject private var viewModel = ContentViewModel(storage: Storage.shared) var body: some View { ZStack { Color.primary .edgesIgnoringSafeArea(.all) CustomTabBarContainerView(selection: $viewModel.selection) { NavigationStack { HomeView() .navigationTitle("Home") } .tabBarItem(tab: .home, selection: $viewModel.selection) NavigationStack { SecondView() .navigationTitle("Favorites") } .tabBarItem(tab: .voice, selection: $viewModel.selection) } .disabled(showOnboarding) } .onChange(of: showOnboarding) { isPresented in if !isPresented { viewModel.finishOnboarding() } } .onAppear { withAnimation { showOnboarding = !Storage.shared.isOnboardingFinished } } .onChange(of: dismissOnboarding) { newValue in if newValue { withAnimation { showOnboarding = false } } }.sheet(isPresented: $showOnboarding) { OnboardingContentView(dismiss: $dismissOnboarding) } }}#Preview { ContentView()}Then I have
struct CustomTabBarContainerView<Content: View>: View { // MARK: - Private Properties private let content: Content @State private var tabs: [TabBarItem] = [] @Binding private var selection: TabBarItem var body: some View { VStack(spacing: 0) { ZStack { content } CustomTabBarView(tabs: tabs, selection: $selection) } .onPreferenceChange(TabBarItemPreferenceKey.self, perform: { tabs in self.tabs = tabs }) } init(selection: Binding<TabBarItem>, @ViewBuilder content: () -> Content) { self._selection = selection self.content = content() } } #Preview { CustomTabBarContainerView(selection: .constant(.home)) { Color.gray } }and: struct TabBarHeightPreferenceKey: PreferenceKey { static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = max(value, nextValue()) }}struct CustomTabBarView: View { let tabs: [TabBarItem] @Binding var selection: TabBarItem @State var tabBarHeight: CGFloat = 0 var body: some View { RoundedRectangle(cornerRadius: 50) .fill(Color.gray.opacity(0.2)) .overlay( HStack(spacing: 0) { ForEach(tabs, id: \.self) { tab in tabView(tab: tab) .getDimensions { dimensions in tabBarHeight = dimensions.height } .onTapGesture { switchTo(tab: tab) } } } ) .frame(height: tabBarHeight) .padding(.horizontal, 20) }}// MARK: - Private Methodsprivate extension CustomTabBarView { func tabView(tab: TabBarItem) -> some View { VStack { Image(systemName: tab.iconName) .font(.subheadline) Text(tab.title) .font(.system(size: 10, weight: .semibold, design: .rounded)) } .frame(maxWidth: .infinity) .padding(.top, 25) .padding(.bottom,25) .foregroundStyle(selection == tab ? tab.color : .gray) } func switchTo(tab: TabBarItem) { selection = tab }}#Preview { VStack { Spacer() CustomTabBarView(tabs: TabBarItem.allCases, selection: .constant(.home)) }}I know this is a lot of code, but I really can't figure out which part of code is relevant here. The thing is, each time I switch the tab, I have my HomeView and SecondView re-creating (aka their inits are called each time). This seems to me like some quite of a mistake in some part of my code, cause if change my custom tab bar, to native TabBar, these views don't get initialized every time, but only once. I guess I am missing here something pretty crucial?
Also here is the extension of View that I use to get a dimensions a TabBar and update its "background":
func getDimensions(action: @escaping (_ rect: CGRect) -> Void) -> some View { self.background( GeometryReader { geometry in Color.clear .preference(key: ViewDimensionsPreferenceKey.self, value: geometry.frame(in: .global)) .onPreferenceChange(ViewDimensionsPreferenceKey.self) { value in action(value) } } ) }