SwiftUI Map mapType hybrid / satellite - possible?

SwiftUI Map() doesn't provide a way of changing mapType from .standard. Some older (1yr+) StackOverflow posts claim being able to set mapType by using a custom initialiser or an extension, but neither approach seems to work for my needs and set up (Swift 5, iOS 15 beta, Xcode 13).

When I use .onAppear() { MKMapView.appearance().mapType = mapStyle}, where mapStyle is a var of the View, the style is set (e.g. to .hybrid) successfully on the first invocation of the view (the map) but not subsequent ones, which are as .standard. I can't think of any way of solving this.

I could use MapKit with UIViewRepresentable, which I've done a few times before with other projects, but it'd be nice to use "pure" SwiftUI.

Any ideas?

Cheers, Michaela

PS - the reason for wanting satellite or hybrid is that I often need to return to a location in a forest area with poor, or non existent, trail mapping.

Post not yet marked as solved Up vote post of AncientCoder Down vote post of AncientCoder
3.9k views

Replies

Yes it is possible to change the mapType from hybrid to satellite. The "trick" is to use an ObservableObject to change the state of the view. Here is a basic setup that works well for me, passing the MapModel through an EnvironmentObject. Let us know if this works for you.

import SwiftUI
import Foundation
import MapKit

@main
struct TestApp: App {
    var mapModel = MapModel()
    var body: some Scene {
        WindowGroup {
            ContentView().environmentObject(mapModel)
        }
    }
}

class MapModel: ObservableObject {
    @Published var mapType = MKMapType.standard
    @Published var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 35.685, longitude: 139.7514), span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2))
}


struct ContentView: View {
    @EnvironmentObject var mapModel: MapModel

    @State private var mapType: Int = 0
    @State private var mapTypes = ["Standard", "Satellite", "Hybrid"]

    var body: some View {
        ZStack (alignment: .topLeading) {
            MapView().edgesIgnoringSafeArea(.all)
            mapTools
        }
    }

    var mapTools: some View {
        HStack {
            Spacer()
            Picker(selection: Binding<Int> (
                get: {self.mapType},
                set: {
                    self.mapType = $0
                    self.mapModel.mapType = self.getMapType()
                }
            ), label: Text("")) {
                ForEach(0 ..< mapTypes.count) {
                    Text(self.mapTypes[$0])
                }
            }.pickerStyle(SegmentedPickerStyle())
            .labelsHidden()
            .frame(width: 222, height: 60)
            .clipped()
            Spacer()
        }
    }

    func getMapType() -> MKMapType {
        switch mapType {
        case 0: return .standard
        case 1: return .satellite
        case 2: return .hybrid
        default:
            return .standard
        }
    }

}

struct MapView: UIViewRepresentable  {

    @EnvironmentObject var mapModel: MapModel

    let mapView = MKMapView()

    func makeUIView(context: Context) -> MKMapView {
        mapView.mapType = mapModel.mapType
        mapView.setRegion(mapModel.region, animated: true)
        return mapView
    }

    func updateUIView(_ uiView: MKMapView, context: Context) {
        uiView.mapType = mapModel.mapType
    }

}
  • sorry, I misread your question, even though it is in big letters, "SwiftUI Map()". I pasted my answer for UIViewRepresentable in haste. Unlike most other forum, I cannot find a way to delete my answer.

  • That’s OK and thanks for your interest anyway. Yes, I’ve done a different project that switches maptype using UIVewRepresentable.

    What I don’t understand with my partial solution is why the very first setting of mapType in onAppear works, and thereafter doesn’t.

  • show us your code, maybe there is a solution to your issue.

I'm still dealing with exactly the same problem, so for now I have decided to use the UIViewRepresentable road. Let me know if something improves with the SwiftUI Map.

  • I've gone back to using UIViewRepresentable for all my mapping needs, except that NSGesturerecognizer seems not to work in certain cases with MacOS and Mac Catalyst apps crash if the app uses polylines.

Add a Comment

With iOS 17 beta you finally get the mapStyle functionality.

You can use like this: .mapStyle(.imagery(elevation: .realistic))

Watch the WWDC 2023 session for more insights:

https://developer.apple.com/videos/play/wwdc2023/10043/