Search code examples
swiftswiftuiswiftui-navigationlinkswiftui-toolbar

Is there a way to hide a toolbar in Swift without causing Views to shift?


Please bear with me on this question as I am still learning iOS development using Swift.

I currently have an app where the ContentView is a MapView with a clear toolbar at the top. The toolbar has a single button positioned at .topLeading.

When I press the button, it reveals a side menu and the toolbar is hidden using .toolbarBackground(.hidden, for: .navigationBar). This works as expected.

Within that side menu, I am using a NavigationLink to show different views as needed, and that is where the problem begins. When I press the button to go to the new view, after maybe half a second, the view seems to shift vertically. When I return to the side menu, the elements in my side menu also shift vertically back to their intended spot in a similar manner.

I have narrowed the problem down to using .toolbarBackground(.hidden, for: .navigationBar). If I comment out that bit of code, none of the views or side menu elements shift.

Using .toolbar(showMenu ? .hidden : .visible, for: .navigationBar) also causes views to shift as well.

I got the original idea of this side menu design from this Youtube video and have been modifying it as needed. In the video, there does seem to be a bit of shifting to the background view that the side menu is overlayed on top of, but not to the actual side menu itself.

Here is my code for the ContentView that contains the toolbar:

struct ContentView: View {

    @State private var mapOpacity = 1.0
    @State private var toggled = true
    @State private var showMenu = false
    @State private var locationSheetToggle = false
    
    @ObservedObject var locationManager = LocationManager.sharedLocationManager
    
    var body: some View {
        NavigationStack {
            ZStack {
                ZStack {
                    VStack {
                        MapView().ignoresSafeArea(edges: [.leading, .trailing])
                    }
                    .opacity(toggled ? 1.0 : 0.5)
                    .blur(radius: toggled ? 0.0 : 4.0)
                    
                    //Toggle
                    VStack {
                        Spacer()
                        
                        HStack {
                            Text("Map Toggle")
                            Toggle("Map Toggle", isOn: $toggled).labelsHidden()
                            
                        }
                        .padding(.bottom, 20)
                        .padding(.leading, 10)
                        
                    }
                    .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
                    
                    ZStack {
                        VStack(alignment: .center) {
                            Group {
                                HStack {
                                    Image(systemName: "pause.circle")
                                        .resizable()
                                        .foregroundStyle(.red)
                                        .scaledToFit()
                                    
                                    Image(systemName: "exclamationmark.triangle")
                                        .resizable()
                                        .foregroundStyle(.yellow)
                                        .scaledToFit()
                                }
                                .frame(width: 100, height: 50)
                                
                                Text("Location tracking paused. No new paths will be drawn")
                                    .fontWeight(.medium)
                                    .padding()
                            }
                        }
                        .padding()
                        .background(.ultraThinMaterial)
                        .clipShape(RoundedRectangle(cornerRadius: 25))
                        .transition(.symbolEffect)

                    }
                    .animation(.easeInOut, 
                               value: LocationManager.sharedLocationManager.locationPaused || LocationManager.sharedLocationManager.authStatus == .denied)
                    .frame(width: 250, height: 750, alignment: .top)
                    .opacity(LocationManager.sharedLocationManager.locationPaused || LocationManager.sharedLocationManager.authStatus == .denied ? 1.0 : 0.0)

                }
                
                .onAppear(perform: {
                    if locationManager.authStatus != .authorizedAlways {
                        locationSheetToggle.toggle()
                    }
                }).sheet(isPresented: $locationSheetToggle, content: {
                    LocationRequestView(locationSheetToggle: $locationSheetToggle)
                })
                
                SideMenuView(isShowing: $showMenu)
            }
            .toolbar {
                ToolbarItemGroup(placement: .topBarLeading) {
                    Button(action: {
                        showMenu.toggle()
                    }, label: {
                        Image(systemName: "line.3.horizontal.circle.fill")
                            .foregroundStyle(.primary)
                            .frame(width: 45, height: 45)
                            .background(Color(UIColor.systemGray5))
                            .clipShape(RoundedRectangle(cornerRadius: 10))
                        
                    })
                    .opacity(showMenu ? 0.0 : 1.0)
                }
            }
            .toolbarBackground(.hidden, for: .navigationBar)
        }
    }
}

I suspect my view layout or my misunderstanding of using .hidden is probably what's causing this issue but I can't seem to find any other examples of this behavior.


Solution

  • .navigationBarTitleDisplayMode(.inline) on the toolbar in ContentView seemed to solve the issue. Not sure exactly why so I will do more research but if anyone has any insight that would be greatly appreciated. Thanks!