iosswiftuimapkitswiftui-navigationviewmapkitannotation

SwiftUI MapKit breaks the custom navigation bar


When I use non-deprecated Map() functions, it breaks the custom navigation bar and displays the system's default navigation bar instead. I haven’t been able to find a solution to this issue anywhere.

import SwiftUI

struct CustomNavigationBarView: ViewModifier {
    @Environment(\.presentationMode) var presentationMode
    var showBackButton: Bool 
    
    init(showBackButton: Bool = false) {
        self.showBackButton = showBackButton
        
        let navBarAppearance = UINavigationBarAppearance()
        
        navBarAppearance.titleTextAttributes = [.foregroundColor: UIColor(Color.white)]
        
        UINavigationBar.appearance().standardAppearance = navBarAppearance
        UINavigationBar.appearance().compactAppearance = navBarAppearance
        UINavigationBar.appearance().scrollEdgeAppearance = navBarAppearance
        
        navBarAppearance.configureWithTransparentBackground()
                        
        navBarAppearance.backgroundImage = makeLinearGradient(
                    size: .init(width: 1, height: 1),
                    colors: [.nav1, .nav2]
                )
        navBarAppearance.shadowImage = UIImage()
    }
    
    func body(content: Content) -> some View {
        content
            .navigationBarBackButtonHidden(true)
            .toolbar {
                if showBackButton {
                    ToolbarItem(placement: .navigationBarLeading) {
                        Button(action: {
                            presentationMode.wrappedValue.dismiss()
                        }) {
                            HStack {
                                Image(systemName: "chevron.left")
                                    .foregroundColor(.white)
                            }
                        }
                    }
                }
            }
    }
}

func makeLinearGradient(size: CGSize, colors: [UIColor]) -> UIImage {
    
    let renderer = UIGraphicsImageRenderer(size: size)
    let colors: [CGColor] = colors.map({ $0.cgColor })
    let gradient = CGGradient(
        colorsSpace: CGColorSpaceCreateDeviceRGB(),
        colors: colors as CFArray,
        locations: [0, 1]
    )
    return renderer.image { context in
        if let gradient {
            context.cgContext.drawLinearGradient(
                gradient,
                start: CGPoint(x: 0, y: 1),
                end: CGPoint(x: size.width, y: size.height),
                options: .init()
            )
        }
    }
}
extension View {
    func navigationBarModifier(showBackButton: Bool = false) -> some View {
        self.modifier(CustomNavigationBarView(showBackButton: showBackButton))
            .navigationBarTitleDisplayMode(.inline)

    }
}  

Using the map function in this way preserves the navigation bar, but I’d rather avoid it because it’s deprecated.

let annotations = [
           City(name: "London", coordinate: CLLocationCoordinate2D(latitude: 41.015137, longitude: 28.979530))
       ]
@State private var region = MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: 41.015137, longitude: 28.979530),
        span: MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01))
ZStack(alignment: .bottom) {
       Map(coordinateRegion: $region, annotationItems: annotations) { annotation in
                    MapAnnotation(coordinate: annotation.coordinate) {
                        Image("map-loc")
                            .resizable()
                            .frame(width: 25, height: 33)
                    }
                }
} 

Using this method breaks the custom navigation bar, and the other modern functions also fail to work as expected.

let cameraPosition: MapCameraPosition = .region(.init(center: .init(latitude: 41.015137, longitude: 28.979530), latitudinalMeters: 1300, longitudinalMeters: 1300))

ZStack(alignment: .bottom) {
                Map(initialPosition: cameraPosition) {
                    Annotation("map", coordinate: .appleHQ, anchor: .bottom) {
                        Image(systemName: "map-loc")
                            .resizable()
                    }
                }
}

Solution

  • It might help to add .toolbarBackground(.automatic, for: .navigationBar) to the ZStack that contains the Map.

    Here is an adaption of your example, based on an assumption of how you are applying the view extension:

    struct ContentView: View {
        let cameraPosition: MapCameraPosition = .region(.init(center: .init(latitude: 41.015137, longitude: 28.979530), latitudinalMeters: 1300, longitudinalMeters: 1300))
    
        var body: some View {
            NavigationStack {
                NavigationLink("Go to map") {
                    ZStack(alignment: .bottom) {
                        Map(initialPosition: cameraPosition) {}
                    }
                    .toolbarBackground(.automatic, for: .navigationBar) // 👈 added
                }
                .navigationBarModifier() // 👈 is this how you are using your extension?
            }
        }
    }
    

    When the color array [.nav1, .nav2] is replaced by [.yellow, .orange], it looks like this:

    Screenshot