So I'm trying to click on a button and change the mapType using the MKMapView API, but I can't seem to achieve it.
So here is what I have, we have the MKMapView file:
import SwiftUI
import MapKit
struct MapViewUIKit: UIViewRepresentable {
// Environment Objects
@EnvironmentObject var mainViewModel: MainViewModel
@EnvironmentObject private var mapSettings: MapSettings
// Coordinator function
final class Coordinator: NSObject, MKMapViewDelegate {
// Define this class.
var parent: MapViewUIKit
// Initialize this class.
init(_ parent: MapViewUIKit) {
self.parent = parent
}
// MARK: Display Annotation
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// Unrelated code here.
}
// MARK: Select Annotation
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
// Unrelated code here.
}
}
//
func makeCoordinator() -> Coordinator {
MapViewUIKit.Coordinator(self)
}
// MARK: CREATE MAP
func makeUIView(context: Context) -> MKMapView {
// Create map.
let mapView = MKMapView(frame: .zero)
// Coordinate our delegate.
mapView.delegate = context.coordinator
// Set our region for the map.
mapView.setRegion(DEFAULT_MK_REGION, animated: false)
// Set our map type to standard.
mapView.mapType = .standard
// Show user location
mapView.showsUserLocation = true
return mapView
}
// MARK: UPDATE MAP
func updateUIView(_ uiView: MKMapView, context: Context) {
updateMapType(uiView)
}
// Update our map type on selection.
private func updateMapType(_ uiView: MKMapView) {
switch mapSettings.mapType {
case 0:
if #available(iOS 16.0, *) {
let config = MKStandardMapConfiguration(elevationStyle: elevationStyle(), emphasisStyle: emphasisStyle())
config.pointOfInterestFilter = MKPointOfInterestFilter.excludingAll
config.showsTraffic = false
} else {
// Fallback on earlier versions
}
case 1:
if #available(iOS 16.0, *) {
uiView.preferredConfiguration = MKHybridMapConfiguration(elevationStyle: elevationStyle())
} else {
// Fallback
}
case 2:
if #available(iOS 16.0, *) {
uiView.preferredConfiguration = MKImageryMapConfiguration(elevationStyle: elevationStyle())
} else {
// Fallback on earlier versions
}
default:
break
}
}
// Set the elevation style.
@available(iOS 16.0, *)
private func elevationStyle() -> MKMapConfiguration.ElevationStyle {
if mapSettings.showElevation == 0 {
return MKMapConfiguration.ElevationStyle.realistic
} else {
return MKMapConfiguration.ElevationStyle.flat
}
}
// Set the emphasis style.
@available(iOS 16.0, *)
private func emphasisStyle() -> MKStandardMapConfiguration.EmphasisStyle {
if mapSettings.showEmphasisStyle == 0 {
return MKStandardMapConfiguration.EmphasisStyle.default
} else {
return MKStandardMapConfiguration.EmphasisStyle.muted
}
}
}
Then I have my MapDisplaySheetView, which contains the following buttons:
Here is the code that I am using:
struct MapDisplaySheetView: View {
@ObservedObject var mapSettings = MapSettings()
@Environment(\.dismiss) var dismiss
@State var mapType = 0
@State var showElevation = 0
@State var showEmphasis = 0
@State var mapDisplay: [String] = [
"Standard",
"Hybrid",
"Image",
]
@State var mapElevation: [String] = [
"Realistic",
"Flat",
]
@State var mapEmphasis: [String] = [
"Default",
"Muted",
]
var body: some View {
VStack(spacing: 0) {
// MARK: MapType
HStack {
ForEach(mapDisplay, id: \.self) { item in
VStack {
HStack {
VStack {
Button(action: {
switch item {
case "Standard": mapSettings.mapType = 0
case "Hybrid": mapSettings.mapType = 1
case "Image": mapSettings.mapType = 2
default: mapSettings.mapType = 0
}
print("User has selected \(item) map type.")
}, label: {
ZStack {
Text(item)
.multilineTextAlignment(.center)
}
}) //: Button
} //: VStack
} //: HStack
}
.onChange(of: mapType) { newValue in
mapSettings.mapType = newValue
log.info("The new map type is: \(newValue)")
}
} //: ForEach
} //: HStack
// MARK: Map Elevation
HStack {
ForEach(mapElevation, id: \.self) { item in
VStack {
HStack {
VStack {
Button(action: {
switch item {
case "Realistic": mapSettings.showElevation = 0
case "Flat": mapSettings.showElevation = 1
default: mapSettings.showElevation = 0
}
print("User has selected \(item) map elevation.")
}, label: {
ZStack {
Text(item)
.multilineTextAlignment(.center)
}
}) //: Button
}
}
}
}
ForEach(mapEmphasis, id: \.self) { item in
VStack {
HStack {
VStack {
Button(action: {
switch item {
case "Default": mapSettings.showEmphasisStyle = 0
case "Muted": mapSettings.showEmphasisStyle = 1
default: mapSettings.showEmphasisStyle = 0
}
print("User has selected \(item) map emphasis.")
}, label: {
ZStack {
Text(item)
.multilineTextAlignment(.center)
}
}) //: Button
}
}
}
}
} //: HStack
}
}
}
// Mapping
final class MapSettings: ObservableObject {
@Published var mapType = 0
@Published var showElevation = 0
@Published var showEmphasisStyle = 0
}
I am attempting to use case 0
for the top 3 buttons, which are the mapType
and then case 1
for the bottom 2 left buttons and then case 2
for the bottom 2 right buttons, but I can't seem to get the map to update at all, which I believe there is an issue inside MapViewUIKit
and got the mapType
is set.
Could the issue be with mapView.mapType = .standard
?
I am using this guide as an example: https://holyswift.app/new-mapkit-configurations-with-swiftui/
First, change your settings to a struct (you don't need a reference type in this situation):
struct MapSettings {
var mapType = 0
var showElevation = 0
var showEmphasisStyle = 0
}
Then fix the @ObservedObject
to:
@State var mapSettings = MapSettings()
Then you can do
struct MapViewUIKit: UIViewRepresentable {
let mapSettings: MapSettings
updateUIView
will be called when mapSettings
has changed from the last time this View was init, and then you can use the new values to make any changes to MKMapView
if required.
Another mistake is Coordinator(self)
, self is just a value which is immediately discarded after SwiftUI updates so that won't work, try this structure instead:
struct MyMapView: UIViewRepresentable {
@Binding var userTrackingMode: MapUserTrackingMode
func makeCoordinator() -> Coordinator {
Coordinator()
}
func makeUIView(context: Context) -> MKMapView {
context.coordinator.mapView
}
func updateUIView(_ uiView: MKMapView, context: Context) {
// MKMapView has a strange design that the delegate is called when setting manually so we need to prevent an infinite loop
context.coordinator.userTrackingModeChanged = nil
uiView.userTrackingMode = userTrackingMode == .follow ? MKUserTrackingMode.follow : MKUserTrackingMode.none
context.coordinator.userTrackingModeChanged = { mode in
userTrackingMode = mode == .follow ? MapUserTrackingMode.follow : MapUserTrackingMode.none
}
}
class Coordinator: NSObject, MKMapViewDelegate {
lazy var mapView: MKMapView = {
let mv = MKMapView()
mv.delegate = self
return mv
}()
var userTrackingModeChanged: ((MKUserTrackingMode) -> Void)?
func mapView(_ mapView: MKMapView, didChange mode: MKUserTrackingMode, animated: Bool) {
userTrackingModeChanged?(mode)
}
}
}