How to animate navigationBarHidden in SwiftUI?
Asked Answered
M

4

8
struct ContentView: View {
    @State var hideNavigationBar: Bool = false
    
    var body: some View {
        NavigationView {
            ScrollView {
                VStack {
                    Rectangle().fill(Color.red).frame(height: 50)
                        .onTapGesture(count: 1, perform: {
                            withAnimation {
                                self.hideNavigationBar.toggle()
                            }
                        })
                    VStack {
                        ForEach(1..<50) { index in
                            HStack {
                                Text("Sample Text")
                                Spacer()
                            }
                        }
                    }
                }
            }
            .navigationBarTitle("Browse")
            .navigationBarHidden(hideNavigationBar)
        }
    }
}

When you tap the red rectangle it snaps the navigation bar away. I thought withAnimation{} would fix this, but it doesn't. In UIKit you would do something like this navigationController?.setNavigationBarHidden(true, animated: true).

Tested in xCode 12 beta 6 and xCode 11.7

Marrin answered 4/9, 2020 at 12:18 Comment(1)
I'm still learning animation in SwiftUI... at this current moment in time I understand that we must animate the parent view. Have you tried applying an .animate() view modifier to the ScrollView? (I'd suggest placing it beneath .navigationBarHidden(hideNavigationBar)Holophrastic
V
9

You could try using

.navigationBarHidden(hideNavigationBar).animation(.linear(duration: 0.5)) instead of .navigationBarHidden(hideNavigationBar)

and also move self.hideNavigationBar.toggle() out of the animation block. That is not required if you use the above approach for hiding of navigation bar with animation.

Vichyssoise answered 4/9, 2020 at 13:39 Comment(6)
Hey Andrew did you try your code in xCode because I changed the line into the animation like you said, but it still shows the snappy behavior when pressing the red rectangle.Marrin
I did try the code and it was working fine for me. What version of Xcode are you using. I checked with Xcode 12Vichyssoise
Somehow I needed to reboot. I will mark it as an answer.Marrin
Is still really working? I tried and it does only animate the toolbar items but not the navigation bar itself.Invertase
It does work! Important to understand: the animation modifier must "hit" somehow the NavigationView or it's top level container (here the ScrollView), otherwise the change of hideNavigationBar is not animated. I suppose the navigationBarHidden modifier talks to the NavigationView using the SwiftUI preferences system, therefore any animation modifier applied to the navigationBarHidden modifier is somewhat irrelevant. The modifier applied to the NavigationView/top level container is important.Comber
bruh how does this work lmaoRockery
K
2

I think, the only solution is to use a position function in SwiftUI 2

var body: some View {
    GeometryReader { geometry in
        NavigationView {
            ZStack {
                Color("background")
                    .ignoresSafeArea()
                
                // ContentView
            }
            .navigationBarTitleDisplayMode(.inline)
            .navigationBarItems(leading: logo, trailing: barButtonItems)
            .toolbar {
                ToolbarItem(placement: .principal) {
                    SearchBarButton(placeholder: LocalizedStringKey("home_vc.search_bar.placeholder"))
                        .opacity(isNavigationBarHidden ? 0 : 1)
                        .animation(.easeInOut(duration: data.duration))
                }
                
            }
        }
        .frame(height: geometry.size.height + (isNavigationBarHidden ? 70 : 0))
         // This is the key ⬇
        .position(x: geometry.size.width/2, y: geometry.size.height/2 - (isNavigationBarHidden ? 35 : 0))
        .animation(.easeInOut(duration: 0.38))
        .onTapGesture {
            isNavigationBarHidden.toggle()
        }
    }
}

enter image description here

Koy answered 20/1, 2021 at 10:20 Comment(2)
Where did you get these magic numbers from?Outrage
@Outrage Normally, the navigation height is 44pt, but in my case, I had to customize the navigation. (Note that customizing the navigation is not recommended) So the navigation height value may vary depending on the service.Koy
H
0

I'm still learning animation in SwiftUI but at this stage, I understand that you must animate the parent view.

So your code would become...

struct ContentView: View {

    @State var hideNavigationBar: Bool = false
    
    var body: some View {
        NavigationView {
            ScrollView {
                VStack {
                    Rectangle().fill(Color.red).frame(height: 50)
                        .onTapGesture(count: 1) {
                            self.hideNavigationBar.toggle()
                        }
                    VStack {
                        ForEach(1..<50) { index in
                            HStack {
                                Text("Sample Text")
                                Spacer()
                            }
                        }
                    }
                }
            }
            .navigationBarTitle("Browse")
            .navigationBarHidden(hideNavigationBar)
            .animation(.spring()) // for example
        }
    }
}

Note that the last argument in any function call can be placed into a single closure.

So...

                    .onTapGesture(count: 1, perform: {
                        self.hideNavigationBar.toggle()
                    })

can become...

                    .onTapGesture(count: 1) {
                        self.hideNavigationBar.toggle()
                    }

Simpler syntax in my humble opinion.

Holophrastic answered 4/9, 2020 at 13:51 Comment(3)
Hey Andrew did you try your code in xCode because I copied and paste it, however the snappy behavior still exists.Marrin
@Marrin now that you mention it... I've not actually been able to animate the navigation bar yet. This is a common complaint. I'll do some more research and update my answer - it remains a problem in my iOS 14 targets... One thing I've done successfully and can suggest is, while it is not a solution, to animate elements around the navigation bar. This takes the user's focus away form the nav bar.Holophrastic
Thanks for your input. I submitted it to Apple Feedback you never know.Marrin
R
-1

iOS 17 solution:

struct SomeView: View {
   @State private var hideStatusBar: Bool = false
   
   var body: some View {
      EmptyView()
         .statusBarHidden(hideStatusBar)
         .animation(.linear(duration: 0.5), value: hideStatusBar)
         .onAppear(perform: onAppear)
   }
   
   func onAppear() {
      hideStatusBar = true
   }
}
Rockery answered 15/7, 2024 at 16:18 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.