最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

ios - How do I animate hiding and showing tab bar in SwiftUI? - Stack Overflow

programmeradmin5浏览0评论

In my SwiftUI app I have a basic tab view. For one of the detail views I want to hide the tab bar, displaying it again when a user navigates back. By default the tab bar disappears and reappears gracelessly, popping into and out of existence. I have got the disappearing of the tab view to animate, but reappearing isn't animated and the method I'm using is deprecated. Is there a way to animate showing and hiding the tab bar?

struct ContentView: View {
    @State private var selectedTab: AppTab

    var body: some View {
        TabView(selection: $selectedTab) {
            TabOne()
                .tabItem {
                    Label(AppTab.tabone.title, systemImage: AppTab.tabone.iconName)
                }
                .tab(AppTab.tabone)

            TabTwo()
                .tabItem {
                    Label(AppTab.tabtwo.title, systemImage: AppTab.tabtwo.iconName)
                }
                .tab(AppTab.tabtwo)

            TabThree()
                .tabItem {
                    Label(AppTab.tabthree.title, systemImage: AppTab.tabthree.iconName)
                }
                .tab(AppTab.tabthree)
        }
    }
}

struct TabOne: View {
    var body: some View {
        NavigationStack {
            NavigationLink(destination: {
                Text("Details View")
                    .toolbar(.hidden, for: .tabBar)
                    // Adding animation causes the tab bar to animate when disappearing, but doesn't animate when the user navigates back and the tab bar is shown again.
                    // animation is also deprecated
                    .animation(.easeInOut(duration: 0.5))
            }, label: {
                Label("Details", systemImage: "person.crop.circle")
            }
        }
    }
}

I have tried to use withAnimation, but that didn't work.

.toolbar(withAnimation(.easeInOut(duration: 0.5)) { .hidden }, for: .tabBar)

In my SwiftUI app I have a basic tab view. For one of the detail views I want to hide the tab bar, displaying it again when a user navigates back. By default the tab bar disappears and reappears gracelessly, popping into and out of existence. I have got the disappearing of the tab view to animate, but reappearing isn't animated and the method I'm using is deprecated. Is there a way to animate showing and hiding the tab bar?

struct ContentView: View {
    @State private var selectedTab: AppTab

    var body: some View {
        TabView(selection: $selectedTab) {
            TabOne()
                .tabItem {
                    Label(AppTab.tabone.title, systemImage: AppTab.tabone.iconName)
                }
                .tab(AppTab.tabone)

            TabTwo()
                .tabItem {
                    Label(AppTab.tabtwo.title, systemImage: AppTab.tabtwo.iconName)
                }
                .tab(AppTab.tabtwo)

            TabThree()
                .tabItem {
                    Label(AppTab.tabthree.title, systemImage: AppTab.tabthree.iconName)
                }
                .tab(AppTab.tabthree)
        }
    }
}

struct TabOne: View {
    var body: some View {
        NavigationStack {
            NavigationLink(destination: {
                Text("Details View")
                    .toolbar(.hidden, for: .tabBar)
                    // Adding animation causes the tab bar to animate when disappearing, but doesn't animate when the user navigates back and the tab bar is shown again.
                    // animation is also deprecated
                    .animation(.easeInOut(duration: 0.5))
            }, label: {
                Label("Details", systemImage: "person.crop.circle")
            }
        }
    }
}

I have tried to use withAnimation, but that didn't work.

.toolbar(withAnimation(.easeInOut(duration: 0.5)) { .hidden }, for: .tabBar)
Share Improve this question edited Mar 10 at 15:01 Benzy Neez 23.7k3 gold badges15 silver badges45 bronze badges asked Mar 10 at 11:43 PieterPieter 2171 gold badge4 silver badges16 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 1

When the tab bar is hidden, two changes happen:

  1. First, the tab bar itself is removed.
  2. After the tab bar has been removed, the space it was using is made available to the page content.

It seems that these changes happen in sequence, not simultaneously. This probably means, the animations can only happen in sequence too:

  • To animate the removal of the tab bar, use a state variable. This is described in the answers to Animating TabBar visibility transitions in SwiftUI (and repeated in another answer here).

    I found it works well to change the state value withAnimation in separate .onAppear callbacks for the navigation link and the detail content.

  • To animate the change in content height, the content can be nested in a GeometryReader. This is used to measure the height available, which is then set as maxHeight on the nested content.

    I found that it works best to use a separate GeometryReader for the detail view.

Here is how the changes can be applied to the example:

struct TabOne: View {
    @State var toolbarVisibility = Visibility.visible

    var body: some View {
        GeometryReader { outer in
            NavigationStack {
                NavigationLink {
                    GeometryReader { inner in
                        Text("Details View")
                            .frame(maxWidth: .infinity, maxHeight: inner.size.height)
                            .animation(.default, value: inner.size.height)
                    }
                    .onAppear {
                        withAnimation { toolbarVisibility = .hidden }
                    }
                } label: {
                    Label("Details", systemImage: "person.crop.circle")
                }
                .onAppear {
                    withAnimation { toolbarVisibility = .visible }
                }
            }
            .toolbar(toolbarVisibility, for: .tabBar)
            .frame(maxWidth: .infinity, maxHeight: outer.size.height)
            .animation(.default, value: outer.size.height)
        }
    }
}


If you want to avoid the staggered animation, then another way to solve would be to use a custom tab bar. An example implementation can be found in this answer (it was my answer).

The change to a custom tab bar is quite easy in your case, because you are already using an enum for the tab options.

Here is the fully updated example to show it working. The background to the tab bar is commented out, so that it looks the same as the previous example. You might want to implement some logic that decides when to show or hide the background.

struct TabLabelStyle: LabelStyle {
    let isSelected: Bool

    func makeBody(configuration: Configuration) -> some View {
        VStack(spacing: 4) {
            configuration.icon
                .imageScale(.large)
            configuration.title
                .font(.caption)
        }
        .symbolVariant(isSelected ? .fill : .none)
        .foregroundStyle(isSelected ? Color.accentColor : .secondary)
        .frame(maxWidth: .infinity)
    }
}

struct CustomTabBar: View {
    @Binding var selection: AppTab

    var body: some View {
        HStack {
            ForEach(AppTab.allCases, id: \.self) { type in
                Button {
                    withAnimation(.spring) {
                        selection = type
                    }
                } label: {
                    Label(type.title, systemImage: type.iconName)
                        .labelStyle(TabLabelStyle(isSelected: selection == type))
                }
            }
        }
        .padding()
//        .background(.bar)
//        .overlay(alignment: .top) { Divider() }
    }
}

struct ContentView: View {
    @State private var selectedTab = AppTab.tabone
    @State private var toolbarVisibility = Visibility.visible

    var body: some View {
        TabView(selection: $selectedTab) {
            TabOne(toolbarVisibility: $toolbarVisibility)
                .tag(AppTab.tabone)
                .toolbarVisibility(.hidden, for: .tabBar)

            TabTwo()
                .tag(AppTab.tabtwo)
                .toolbarVisibility(.hidden, for: .tabBar)

            TabThree()
                .tag(AppTab.tabthree)
                .toolbarVisibility(.hidden, for: .tabBar)
        }
        .safeAreaInset(edge: .bottom) {
            if toolbarVisibility == .visible {
                CustomTabBar(selection: $selectedTab)
                    .transition(.asymmetric(
                        insertion: .push(from: .bottom),
                        removal: .push(from: .top)
                    ))
            }
        }
    }
}

struct TabOne: View {
    @Binding var toolbarVisibility: Visibility

    var body: some View {
        NavigationStack {
            NavigationLink {
                Text("Details View")
                    .onAppear {
                        withAnimation { toolbarVisibility = .hidden }
                    }
            } label: {
                Label("Details", systemImage: "person.crop.circle")
            }
            .onAppear {
                withAnimation { toolbarVisibility = .visible }
            }
        }
    }
}

Depend the visibility of the toolbar to a value and change it with animation when needed:

@State var isTabBarVisible = true
.toolbar(isTabBarVisible ? .visible : .hidden, for: .tabBar)
withAnimation {
    isTabBarVisible.toggle()
}
发布评论

评论列表(0)

  1. 暂无评论