I would like to conditionally render TabViewBottomAccessory
because I don't want to show it when I am drilling down. When going to the child view I want to hide both tab view (which I achieved by .toolbarVisibility(.hidden, for: .tabBar)
) and its accessory.
@Environment(\.tabViewBottomAccessoryPlacement)
doesn't have a setter.tabViewBottomAccessory
down the tree has no effect@Environment(\.isTabViewBottomAccessoryShown)
has no effect on it#Preview("PreviewView") {
PreviewView()
}
enum Destination {
case child
}
struct PreviewView: View {
@State var tab: Int = 0
var body: some View {
TabView(selection: $tab) {
Tab(value: 0) {
NavigationStack {
List {
NavigationLink("To Child", value: Destination.child)
}
.navigationDestination(for: Destination.self) {
switch $0 {
case .child:
ChildView()
// 1
.environment(\.isTabViewBottomAccessoryShown, false)
.toolbarVisibility(.hidden, for: .tabBar)
// 2
.tabViewBottomAccessory {
EmptyView()
}
}
}
}
} label: {
Label("Tab 1", systemImage: "checkmark")
}
}
.tabViewBottomAccessory {
TabViewBottomAccessory()
}
}
}
struct TabViewBottomAccessory: View {
@Environment(\.tabViewBottomAccessoryPlacement) var placement
// 3
@Environment(\.isTabViewBottomAccessoryShown) var isTabViewBottomAccessoryShown
var body: some View {
Text("'\(placement)' '\(isTabViewBottomAccessoryShown)'")
}
}
struct ChildView: View {
var body: some View {
List {
Text("Child")
}
}
}
//
public struct TabViewBottomAccessoryVisibilityKey: EnvironmentKey {
public static let defaultValue: Bool = true
}
extension EnvironmentValues {
public var isTabViewBottomAccessoryShown: Bool {
get { self[TabViewBottomAccessoryVisibilityKey.self] }
set { self[TabViewBottomAccessoryVisibilityKey.self] = newValue }
}
}
Environment values don't work because the tab bar bottom accessory is not a descendent of the navigation destination. Environment values are only propagated down the hierarchy.
The tab bar bottom accessory is actually a cousin of the navigation destination - they share a common ancestor (the tab view) but one is not a parent of another. I would use a PreferenceKey
to propagate the "preference" of whether they want the accessory view to be hidden, to PreviewView
.
struct ShouldHideBottomAccessory: PreferenceKey {
static let defaultValue = false
static func reduce(value: inout Bool, nextValue: () -> Bool) {
value = value || nextValue()
}
}
enum Destination {
case child
}
struct PreviewView: View {
@State private var tab: Int = 0
@State private var shouldHide = false
var body: some View {
TabView(selection: $tab) {
Tab(value: 0) {
NavigationStack {
List {
NavigationLink("To Child", value: Destination.child)
}
.navigationDestination(for: Destination.self) {
switch $0 {
case .child:
ChildView()
}
}
}
} label: {
Label("Tab 1", systemImage: "checkmark")
}
}
.onPreferenceChange(ShouldHideBottomAccessory.self) {
shouldHide = $0
}
.tabViewBottomAccessory {
if !shouldHide {
TabViewBottomAccessory()
}
}
}
}
struct ChildView: View {
var body: some View {
List {
Text("Child")
}
.preference(key: ShouldHideBottomAccessory.self, value: true)
}
}
struct TabViewBottomAccessory: View {
var body: some View {
Text("Accessory")
}
}
Note that this doesn't work in beta 2 (the app crashes when you navigate back to the root), but it does work in beta 3.