7

This ought to be straightforward enough, but I cannot find out how to place a background behind a NavigationStack. With NavigationView, it was simply a matter of embedding in a ZStack with the background view called before the NavigationView (as described in an older post: How change background color if using NavigationView in SwiftUI?) The same technique does not work for me with NavigationStack. Here's what I have:

struct MyAngularGradient: View {
    var body: some View {
        ZStack {
            AngularGradient(gradient: Gradient(colors: [.red, .orange , .yellow, .green, .cyan, .blue, .indigo, .purple, .red]), center: .leading)
            AngularGradient(gradient: Gradient(colors: [.red, .orange , .yellow, .green, .cyan, .blue, .indigo, .purple, .red]), center: .leading)
                .offset(x: -8)
        }
        .ignoresSafeArea()
    }
}

var body: some View {
    ZStack {
        MyAngularGradient()
        NavigationStack {
            ...
        }
        .navigationViewStyle(.stack)
     } // end ZStack
}

FYI, I've used the same MyAngularGradient()in other apps (with NavigationView) Any ideas? Thanks.

3 Answers 3

8

The closest I've managed to get so far is this:

struct MyView: View {

    var body: some View {
        NavigationStack {
            ZStack {
                Color.pink.ignoresSafeArea()

                List {
                    NavigationLink("Hello") {
                        Text("Hello")
                    }
                }
                .navigationTitle("Title")
            }
        }
        .scrollContentBackground(.hidden)
    }

}

Which is somewhat dissatisfying for the following reasons:

  1. When you scroll up the navigation bar appears, as expected, but ruins the effect imo.

I guess you can experiment changing this in a limited way using UINavigationBarAppearance() by updating it in the constructor of the view.

Non-scrolled image of the above Scrolled version of the above

  1. You can't apply a background to the whole app if you have multiple NavigationStackView based views in a TabView. (My example above was in a TabView)

  2. When a new view is pushed on the stack the custom background disappears.

  3. Only works on iOS 16+ due to the .scrollContentBackground(.hidden) modifier.

  4. Does not work if you use ForEach to populate the List. Interestingly if you debug the hierarchy with and without the ForEach you'll see that SwiftUI adds a new view controller which is opaque in the case of ForEach.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you brindy for taking the time to answer. I tried your suggestion with immediate but limited success. I will keep experimenting, and if/when I get the effect I'm hoping for, I will be sure to update this post. In the meantime, thanks for getting me headed in (what I think is) the right direction.
0

You can just apply .background to the main content.

  • Use .frame to extend the size of the main content to the full screen size, if necessary.
  • A ZStack is not needed.
NavigationStack {
    Text("NavigationStack")
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background { MyAngularGradient() }
}
.navigationViewStyle(.stack)

Screenshot

1 Comment

This doesn't answer the question. Once you push another view onto the stack, the background you set on the outgoing view will move away with it. The effect we're after here is a background that is unaffected by the mutations of the stack's path, and allowing that background to be always visible.
0

The other solutions work fine if you just want the same background in all your sub sheets - but if you truly need transparency, for example because you're using a material, the following solution should work for you.

You can use Introspect to work around the problem. Introspect allows you to find that view and manually change the color to .clear and also remove the shadow. Note that the push and pop animations do not work well with a clear background - so your result will look like this.

Animated push transition

I worked around this problem by using NavigationTransitions to change the transition to a cross fade. The result looks fine:

Animated crossfade transition

Here is the source for the cross fade View. You can comment in the code and remove the .navigationTransition if you prefer the push/pop slide effect.

import MapKit
import NavigationTransitions
import SwiftUI
import SwiftUIIntrospect

struct ContentView: View {

    @State var navigationPath: [SubSheet] = []
    var body: some View {
        Map(initialPosition: .region(.applePark))
            .ignoresSafeArea(.all)

            .sheet(isPresented: .constant(true)) {
                NavigationStack(path: $navigationPath) {
                    VStack {
                        Text("Test")
                        NavigationLink(
                            "Activate SubSheet", value: SubSheet.subSheetOne)
                    }
                    .navigationDestination(for: SubSheet.self) { something in

                        Text("SubSheet")
                            .introspect(
                                .navigationStack, on: .iOS(.v18),
                                scope: .ancestor
                            ) { something in

                                let allsubviews = something.view.allSubViews
                                allsubviews.forEach { view in
                                    if view.backgroundColor == .systemBackground
                                        && view.debugDescription.contains(
                                            "NavigationStackHostingController")
                                    {

                                        // We found the child view that has the systemBackground we don't want, so we remove it.
                                        view.backgroundColor = nil
                                    }

                                    // if you want to use the push/pop transition, you can comment in
                                    // this code to remove the shadow too.
                                    /*
                                     if view.backgroundColor != .black && view.backgroundColor?.cgColor.alpha == 0.1 {
                                        print("changing from \(view.backgroundColor)")
                                        view.backgroundColor = nil
                                     }
                                     */
                                }
                            }
                            .navigationTitle("SubSheet")

                    }

                }
                .navigationTransition(.fade(.cross))  // remove this if you really want the push/pop slide effect

                .scrollContentBackground(.hidden)
                .backgroundStyle(.clear)
                .presentationDetents([.fraction(0.33)])
                .presentationBackgroundInteraction(
                    .enabled(upThrough: .fraction(0.33))
                )
                .presentationBackground(.ultraThickMaterial)

            }

    }
}

enum SubSheet: Hashable {
    case subSheetOne
}

extension UIView {

    /// Retrieving All Subviews from a UIView
    fileprivate var allSubViews: [UIView] {
        return subviews.flatMap { [$0] + $0.allSubViews }
    }
}

extension MKCoordinateRegion {
    /// Apple Mark Region
    static var applePark: MKCoordinateRegion {
        let center = CLLocationCoordinate2D(
            latitude: 37.334606, longitude: -122.009102)
        return .init(
            center: center, latitudinalMeters: 10000, longitudinalMeters: 10000)
    }
}

#Preview {
    ContentView()
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.