swiftswiftuimapkitswiftui-listswiftui-navigationstack

SwiftUI List/ScrollView x MapKit - Map adds gradient/material toolbar (navigation bar) and overrides its design


I'm working on iOS 17+ app in SwiftUI, which uses MapKit. MapKit's Map view adds a gradient/material view on Toolbar, which is presented on second and every next navigation.

Unexpected bahavior

I would like to get the first's navigation behavior, which is no background and no shadow image (navbar divider).

This issue is 100% related to the Map view - commenting it out resolves the issue, but also removes the functionality, which I need.

Code:

struct HomeView: View {
    var body: some View {
        NavigationStack {
            Content(/* properties */)
        }
    }
}

struct Content: View {
    // Properties ...

    var body: some View {
        Group {
            if array.isEmpty {
                EmptyStateView()
            } else {
                ListView(data: array, onDelete: onDelete)
            }
        }
        .accentBackground(strong: true) // << Gradient background
        .navigationTitle("Title")
        .toolbar {
            ToolbarItem(placement: .topBarTrailing) {
                AddButton()
            }
        }
    }
}

struct ListView: View {
    // Properties ...

    var body: some View {
        List {
            ForEach(data) { list in
                Row(/* properties */)
            }
            .onDelete(perform: onDelete)
        }
        .scrollContentBackground(.hidden)
    }
}

struct Row: View {
    // Properties ...

    var body: some View {
        NavigationLink(destination: DetailsView(list: list)) {
            VStack(alignment: .leading) {
                Text(list.title)
                    .font(.headline)
                Text(/* sublabel */)
                    .font(.subheadline)
                    .foregroundStyle(.secondary)
            }
        }
    }
}

struct DetailsView: View {
    // Properties ...

    var body: some View {
        List {
            // Some conditional subviews ...

            if let location = list.location {
                Section {
                    // Edit mode conditional remove button ...

                    PresentableMap(location: location)
                        .frame(height: 300)
                }
            }
        }
        .scrollContentBackground(.hidden)
        .accentBackground(strong: true)
        .navigationTitle(list.title)
    }
}

struct PresentableMap: View {
    let location: ListLocation

    private var coordinates: CLLocationCoordinate2D {
        .init(latitude: location.latitude, longitude: location.longitude)
    }

    var body: some View {
        // MapKit View ⬇️
        Map(position: .constant(.camera(.init(centerCoordinate: coordinates, distance: 200))), selection: .constant(location)) {
            Marker(coordinate: coordinates) {
                Image(systemName: "mappin")
            }
        }
        .clipShape(RoundedRectangle(cornerRadius: 8))
        .allowsHitTesting(false)
    }
}

What I tried:

On the gif above you can see that overriding the background respects the safe area, however, the toolbar material added by Map view ignores the safe area.

What do I expect:


Solution

  • It seems to help to add a modifier for .toolbarBackgroundVisibility that sets the visibility to .automatic, even though you would expect this to be the default:

    // DetailsView
    
    List {
        // Some conditional subviews ...
    
        if let location = list.location {
            Section {
                // Edit mode conditional remove button ...
    
                PresentableMap(location: location)
                    .frame(height: 300)
            }
        }
    }
    .scrollContentBackground(.hidden)
    .accentBackground(strong: true)
    .navigationTitle(list.title)
    .toolbarBackgroundVisibility(.automatic, for: .navigationBar) // 👈 here
    

    The modifier .toolbarBackgroundVisibility was introduced in iOS 18. For iOS 16 and 17, use .toolbarBackground instead:

    .toolbarBackground(.automatic, for: .navigationBar)