SwiftUI - Navigation Bar Fails to Switch From Scroll Edge Appearance

It took me a while to come up with a minimum reproducible example, but I'm seeing an issue where the OS default Navigation Bar is transparent, and stays transparent even after scrolling.

import SwiftUI

struct MainView: View {
    var body: some View {
        ScrollView {
            Text("Text")
        }
        .frame(maxWidth: .infinity)
        .navigationBarTitleDisplayMode(.inline)
        .navigationTitle("Main View")
    }
}

struct TabBarView: View {
    var body: some View {
        Color.pink
            .frame(maxWidth: .infinity)
            .frame(height: 55)
    }
}

struct ContentView: View {
    var body: some View {
        NavigationView {
            VStack(spacing: 0) {
                MainView()

                TabBarView()  // Commenting this out fixes the problem.
            }
            .background(.red) // Commenting this out _also_ fixes the problem.
        }
    }
}

I've found two separate clues—one seems to be related to the custom tab bar we have implemented, and the other is related to having a background set on the main view. When removing either or both, the issue goes away. The tab bar bit maybe makes sense—I'm guessing since the ScrollView is nested, SwiftUI is unable to figure out that the navigation bar should listen to its position? But the background bit has me completely dumbfounded.

Anyone have any ideas for how I might work around this? Ideally I'd have some way to point the NavigationView to the ScrollView it should be associated to. Thank you!

Replies

You might want to consider implementing a Navigation Bar Appearance ViewModifier and applying it to your VStack

struct MainView: View {
    var body: some View {
        ScrollView {
            Text("Text")
        }
        .frame(maxWidth: .infinity)
    }
}

struct TabBarView: View {
    var body: some View {
        Color.pink
            .frame(maxWidth: .infinity)
            .frame(height: 55)
    }
}

struct ContentView: View {
    var body: some View {
        NavigationView {
            VStack(spacing: 0) {
                MainView()

                TabBarView()  // Commenting this out fixes the problem.
            }
            .background(.red) // Commenting this out _also_ fixes the problem.
            .opaqueNavigationBar(
                    foregroundColor: .white,
                    backgroundColor: .systemBrown,
                    tintColor: .systemIndigo,
                    shadowColor: .darkGray)
            .navigationBarTitleDisplayMode(.inline)
            .navigationTitle("Main View")
        }
    }
}

public struct OpaqueNavigationBarAppearance: ViewModifier {
    public init(
        font: UIFont = .preferredFont(forTextStyle: .headline),
        largeTitleFont: UIFont = .preferredFont(forTextStyle: .largeTitle),
        foregroundColor: UIColor = .clear,
        backgroundColor: UIColor = .secondarySystemBackground,
        tintColor: UIColor = .clear,
        shadowColor: UIColor = .clear) {
        let appearance = UINavigationBarAppearance()
        appearance.configureWithOpaqueBackground()
        appearance.titleTextAttributes = [.foregroundColor: foregroundColor, .font: font]
        appearance.largeTitleTextAttributes = [.foregroundColor: foregroundColor, .font: largeTitleFont]
        appearance.shadowColor = shadowColor
        appearance.backgroundColor = backgroundColor
        UINavigationBar.appearance().standardAppearance = appearance
        UINavigationBar.appearance().scrollEdgeAppearance = appearance
        UINavigationBar.appearance().compactAppearance = appearance
        UINavigationBar.appearance().tintColor = tintColor
    }

    public func body(content: Content) -> some View {
        return content
    }
}

public extension View {
    func opaqueNavigationBar(
        font: UIFont = .preferredFont(forTextStyle: .headline),
        largeTitleFont: UIFont = .preferredFont(forTextStyle: .largeTitle),
        foregroundColor: UIColor = .clear,
        backgroundColor: UIColor = .secondarySystemBackground,
        tintColor: UIColor = .clear,
        shadowColor: UIColor = .clear) -> some View {
        ModifiedContent(
            content: self,
            modifier: OpaqueNavigationBarAppearance(
                font: font,
                largeTitleFont: largeTitleFont,
                foregroundColor: foregroundColor,
                backgroundColor: backgroundColor,
                tintColor: tintColor,
                shadowColor: shadowColor))
    }
}

@anils My real code does have a custom navigation bar appearance, but this does not solve anything.

  • @rubencodes - use ZStack(alignment: .bottom) instead of VStack

Add a Comment