Search code examples
swiftuiswiftui-navigationviewios15xcode13

iOS 15 SwiftUI Conditionals on a view with Navigation View makes NavigationBar config to be ignore if navigationViewStyle stack


been searching for this everywhere and can't find anything around this, I believe is a bug, maybe is not.

I need NavigationView with .navigationViewStyle(.stack) to have it stacked on the iPad and make it look the same as the iphone, now suppose you have this view:

import SwiftUI

struct ContentView: View {
    @State var isShowingProfile = false
    @State var isNavigationViewShowing = true
    
    var body: some View {
        if isNavigationViewShowing {
            NavigationView {
                VStack {
                    Button("Simple view") {
                        isNavigationViewShowing = false
                    }
                    .padding()
                    Button("Profile navigation") {
                        isShowingProfile = true
                        
                    }
                    .padding()
                    NavigationLink(
                        destination: ProfileView(),
                        isActive: $isShowingProfile
                    ) {
                        EmptyView()
                    }
                }
                .frame(
                    minWidth: 0,
                    maxWidth: .infinity,
                    minHeight: 0,
                    maxHeight: .infinity
                )
                .background(Color.gray)
                .navigationBarHidden(true)
            }
            .navigationViewStyle(.stack)
        } else {
            VStack {
                Button("Show NavigationView"){
                    isNavigationViewShowing = true
                    
                }
                .padding()
            }
            .frame(
                minWidth: 0,
                maxWidth: .infinity,
                minHeight: 0,
                maxHeight: .infinity
            ).background(Color.yellow)
        }
    }
}

struct ProfileView: View {
    var body: some View {
        Text("This is a profile")
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Well this just show this 3 simple views:

  • The navigationView when you start

The navigationView when you start

  • The Profile view if you tap on "Profile navigation"

Profile view

  • Finally the Simple view which is trigger by the conditional state pressing "Simple view"

Simple view

Up to here is all fine and good.

The problem is when navigate to the "Simple view" and then tap "Show NavigationView" to navigate back to the NavigtionView.

The app opens the first view (NavigationView), but the NavigationView ignores the .navigationBarHidden(true) and just show a big empty space on the top. In fact, it would ignore things like .navigationBarTitleDisplayMode(.inline) and just show the large version of the navigationBar

Broken navigationView

This is working correctly in all iOS 14.x, but on iOS 15.0 seems broken. The behaviour continues to be the same on iOS 15.1 beta.

Any idea whats going on? I'm not really interested in changing the conditionals on the view, because real life app is more complex.

Also, I tried ViewBuilder without any success. And if I take out .navigationViewStyle(.stack) it works all fine on iOS 15, but then the view on the iPad is with the side menu.

Thanks a lot for any tip or help, you should be able to reproduce in simulator and real device.

Video of the explained above


Solution

  • I think the better solution all around is to not have the NavigationView be conditional. There is no reason your conditional can't just live in the NavigationView. You just don't ever want the bar to show. Therefore, this code would seem to meet the requirements:

    struct ContentView: View {
        @State var isShowingProfile = false
        @State var isNavigationViewShowing = true
        
        var body: some View {
            NavigationView {
                Group {
                    if isNavigationViewShowing {
                        VStack {
                            Button("Simple view") {
                                isNavigationViewShowing = false
                            }
                            .padding()
                            Button("Profile navigation") {
                                isShowingProfile = true
                                
                            }
                            .padding()
                            NavigationLink(
                                destination: ProfileView(),
                                isActive: $isShowingProfile
                            ) {
                                EmptyView()
                            }
                        }
                        .frame(
                            minWidth: 0,
                            maxWidth: .infinity,
                            minHeight: 0,
                            maxHeight: .infinity
                        )
                        .background(Color(UIColor.systemGray6))
                    } else {
                        VStack {
                            Button("Show NavigationView"){
                                isNavigationViewShowing = true
                                
                            }
                            .padding()
                        }
                        .frame(
                            minWidth: 0,
                            maxWidth: .infinity,
                            minHeight: 0,
                            maxHeight: .infinity
                        ).background(Color.yellow)
                    }
                }
                .navigationBarHidden(true)
            }
            .navigationViewStyle(.stack)
        }
    }
    

    I used Group simply to put the .navigationBarHidden(true) in the correct place so the code would compile.

    Is this the behavior you are looking for?