Navigation Bar and Tab Bar broken for views with ScrollView and a safe area inset containing another ScrollView

Now that's a mouthful! As of iOS 17 seed 5 and continuing into seed 6, an issue was introduced where default Navigation Bar and Tab Bar behavior breaks when a View is made up of a ScrollView with another ScrollView as the safeAreaInset.

This View renders correctly using the code below, with the Navigation Bar and Tab Bar taking on a material effect when there is content behind it (screenshot 1). However, uncommenting out the ScrollView(.horizontal) so it's active causes the Nav Bar and Tab Bars to be fully transparent (screenshot 2):

        TabView {
            NavigationStack {
                ScrollView(.vertical) {
                    LazyVStack {
                        ForEach(0...100, id: \.self) { _ in
                            Color(uiColor: UIColor.red)
                        }
                    }
                    
                }
                .safeAreaInset(edge: .top, content: {
                    
                   //ScrollView(.horizontal) {
                        HStack {
                            Color.red
                            Color.pink
                            Color.yellow
                        }
                   // }
                    .frame(maxWidth: .infinity)
                    .frame(height: 44)                   
                })
                
                .navigationTitle("Tab 1")
                .navigationBarTitleDisplayMode(.inline)
                
                
                
            }.tabItem { Label("One", systemImage: "bolt") }

I have tried various combinations of the new scrollLayoutTarget() and related modifiers, but I haven’t been able to find a way to have the nav and tab bars maintain their functionality if the Horizontal ScrollView is enabled. This behavior seems like it’s supposed to be supported, because it’s one of the examples featured n this year’s WWDC session “Beyond scroll views” (at 12:34).

It is also worth noting that this issue occurs whether the View is wrapped in a TabView or not. Without the TabView the problem presents the same in the Navigation Bar.

I have submitted this issue as FB12983586

Replies

I assume this is a bug but just in case you want a hacky hack to make it look better...

add a material background to the safeAreaInset and add a inset to the bottom with something to take up the space and add a material background to it.

 
                .safeAreaInset(edge: .top, content: {
                   ScrollView(.horizontal) {
                        HStack {
                            Color.red
                            Color.pink
                            Color.yellow
                        }
                    }
                    .frame(maxWidth: .infinity)
                    .frame(height: 44)
                    .background(.regularMaterial)
                })

                .navigationTitle("Tab 1")
                .navigationBarTitleDisplayMode(.inline)

                .safeAreaInset(edge: .bottom, content: {
                    GeometryReader { _ in
                    }
                        .background(.regularMaterial)
                        .frame(height: 0)
                })
                .navigationTitle("Tab 1")
                .navigationBarTitleDisplayMode(.inline)
                
  • Thanks! I'm hoping this gets resolved in a future beta but I may use an approach like this if I end up needing to ship this layout without a fix 😬

  • @usernameRequired I ended up having to ship a version of this workaround 😅. Thanks again for the response!

Add a Comment

This is still broken in beta 7.

I have a similar problem. The original looks something like this:

#Preview {
    struct AllCurrencyFormatInputPreview: View {
        var body: some View {
            VStack {
                ...
            }
            Text(...)
            ...
        }
    }

    struct TopicsPreview: View {
        var body: some View {
            NavigationStack {
                List {
                    NavigationLink("Currency",
                                   destination: AllCurrencyFormatInputPreview().navigationTitle("Currency"))
                }
            }
        }
    }
    
    return TopicsPreview()
}

The target view goes beyond the navigation bar. My work around is wrapping the content of AllCurrencyFormatInputPreview with a ScrollView. That solves my problem.

    struct AllCurrencyFormatInputPreview: View {
        var body: some View {
            ScrollView {
                VStack {
                    ...
                }
                Text(...)
                ...
            }
        }
    }