Swiftui how to use MKOverlayRenderer?
You need to specify a delegate if you want mapView(_:rendererFor:)
to be called:
struct MapView: UIViewRepresentable {
@Binding var route: MKPolyline?
let mapViewDelegate = MapViewDelegate()
func makeUIView(context: Context) -> MKMapView {
MKMapView(frame: .zero)
}
func updateUIView(_ view: MKMapView, context: Context) {
view.delegate = mapViewDelegate // (1) This should be set in makeUIView, but it is getting reset to `nil`
view.translatesAutoresizingMaskIntoConstraints = false // (2) In the absence of this, we get constraints error on rotation; and again, it seems one should do this in makeUIView, but has to be here
addRoute(to: view)
}
}
private extension MapView {
func addRoute(to view: MKMapView) {
if !view.overlays.isEmpty {
view.removeOverlays(view.overlays)
}
guard let route = route else { return }
let mapRect = route.boundingMapRect
view.setVisibleMapRect(mapRect, edgePadding: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10), animated: true)
view.addOverlay(route)
}
}
class MapViewDelegate: NSObject, MKMapViewDelegate {
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
let renderer = MKPolylineRenderer(overlay: overlay)
renderer.fillColor = UIColor.red.withAlphaComponent(0.5)
renderer.strokeColor = UIColor.red.withAlphaComponent(0.8)
return renderer
}
}
Used like so:
struct ContentView : View {
@State var route: MKPolyline?
var body: some View {
MapView(route: $route)
.onAppear {
self.findCoffee()
}
}
}
private extension ContentView {
func findCoffee() {
let start = CLLocationCoordinate2D(latitude: 37.332693, longitude: -122.03071)
let region = MKCoordinateRegion(center: start, latitudinalMeters: 2000, longitudinalMeters: 2000)
let request = MKLocalSearch.Request()
request.naturalLanguageQuery = "coffee"
request.region = region
MKLocalSearch(request: request).start { response, error in
guard let destination = response?.mapItems.first else { return }
let request = MKDirections.Request()
request.source = MKMapItem(placemark: MKPlacemark(coordinate: start))
request.destination = destination
MKDirections(request: request).calculate { directionsResponse, _ in
self.route = directionsResponse?.routes.first?.polyline
}
}
}
}
Yielding:
Display overlay view when selecting annotation in SwiftUI
You want to know the selected annotation in your SwiftUI view. So you have to store it somewhere. Declare a @State
:
struct ContentView: View {
let locations: [MKAnnotation]
@State private var selectedLocation: MKAnnotation?
var body: some View {
// ... //
}
}
Now in your wrapper (UIViewRepresentable
) you have to make a binding with this MKAnnotation?
:
struct MapView: UIViewRepresentable {
@Binding var selectedLocation: MKAnnotation? // HERE
let annotations: [MKAnnotation]
func makeUIView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.region = // .... //
mapView.addAnnotations(annotations)
mapView.delegate = context.coordinator
return mapView
}
func updateUIView(_ view: MKMapView, context: Context) {
// .... //
}
Now you should be able to access this variable in your Delegate (Coordinator). For that you have to pass the UIViewRepresentable to the Coordinator:
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, MKMapViewDelegate {
var parent: MapView
init(_ parent: MapView) {
self.parent = parent
}
And finally in func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView)
you can copy the MKAnnotation in parent.selectedLocation
.
With the @Binding
this MKAnnotation
is now accessible in your parent view (ContentView
). You can display its properties in your DetailView
.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// ... //
}
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
parent.selectedLocation = view.annotation
}
}
}
For example :
struct ContentView: View {
let locations: [MKAnnotation]
@State private var selectedLocation: MKAnnotation?
var body: some View {
VStack {
Text("\(selectedLocation?.coordinate.latitude ?? 0)")
// Don't forget the Binding : $selectedLocation
MapView(selectedLocation: $selectedLocation, annotations: locations)
}
}
}
iOS MapKit - Which overlay was tapped
Ok, I found a solution if anyone else will ever need it.
- I added a UITapGestureRecognizer to my map view instance.
- I used the MKMapView's convert(_:toCoordinateFrom:) to convert the touch point to the map coordinates
- I created a MKMapPoint from that coordinate and checked if the MKPolygon renderer path contains the point
- For the MKPolygon, being a MKShape after all, I used the .title property to assign my zone_id value parsed from the GeoJSON.
So I was able to identify which polygon was tapped.
import SwiftUI
import MapKit
struct MapView: UIViewRepresentable {
var tapCoordinates: Binding<CLLocationCoordinate2D>
var polygonID: Binding<String>
@EnvironmentObject var mapData: MapViewModel
@State var restrictions: [MKOverlay] = []
@State var restrictionsData: [RestrictionInfo] = []
func makeCoordinator() -> Coordinator {
return MapView.Coordinator(self, tapCoordinatesBinding: tapCoordinates, polygonTitle: polygonID)
}
func makeUIView(context: Context) -> MKMapView {
mapData.mapView.delegate = context.coordinator
let view = mapData.mapView
view.showsUserLocation = true
view.delegate = context.coordinator
mapData.showRestrictedZones { (restriction) in
self.restrictions = restriction
view.addOverlays(self.restrictions)
}
return view
}
func updateUIView(_ uiView: MKMapView, context: Context) {
}
class Coordinator: NSObject, MKMapViewDelegate, UIGestureRecognizerDelegate {
var parent: MapView
var polygonTitle: Binding<String>
var gRecognizer = UITapGestureRecognizer()
var tapCoordinatesBinding: Binding<CLLocationCoordinate2D>
var coordinate = CLLocationCoordinate2D()
init(_ parent: MapView, tapCoordinatesBinding: Binding<CLLocationCoordinate2D>, polygonTitle: Binding<String>) {
self.parent = parent
self.tapCoordinatesBinding = tapCoordinatesBinding
self.polygonTitle = polygonTitle
super.init()
self.gRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapHandler))
self.gRecognizer.delegate = self
self.parent.mapData.mapView.addGestureRecognizer(gRecognizer)
}
@objc func tapHandler(_ gesture: UITapGestureRecognizer) {
// position on the screen, CGPoint
let location = gRecognizer.location(in: self.parent.mapData.mapView)
// position on the map, CLLocationCoordinate2D
coordinate = self.parent.mapData.mapView.convert(location, toCoordinateFrom: self.parent.mapData.mapView)
tapCoordinatesBinding.wrappedValue = coordinate
for overlay: MKOverlay in self.parent.mapData.mapView.overlays {
if let polygon = overlay as? MKPolygon {
let renderer = MKPolygonRenderer(polygon: polygon)
let mapPoint = MKMapPoint(coordinate)
let rendererPoint = renderer.point(for: mapPoint)
if renderer.path.contains(rendererPoint) {
print("Tap inside polygon")
print("Polygon \(polygon.title ?? "no value") has been tapped")
polygonTitle.wrappedValue = polygon.title!
}
}
}
}
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
if let polygon = overlay as? MKPolygon {
let renderer = MKPolygonRenderer(polygon: polygon)
renderer.fillColor = UIColor.purple.withAlphaComponent(0.2)
renderer.strokeColor = .purple.withAlphaComponent(0.7)
renderer.lineWidth = 2
return renderer
}
return MKOverlayRenderer(overlay: overlay)
}
}
}
import SwiftUI
import MapKit
// All Map Data Goes here...
class MapViewModel: NSObject, ObservableObject {
@Published var mapView = MKMapView()
func restrictionsInfo(completion: @escaping ([RestrictionInfo]) -> ()) {
guard let url = URL(string: "https://flightplan.romatsa.ro/init/static/zone_restrictionate_uav.json") else {
fatalError("Unable to get geoJSON") }
downloadData(fromURL: url) { (returnedData) in
if let data = returnedData {
var geoJson = [MKGeoJSONObject]()
do {
geoJson = try MKGeoJSONDecoder().decode(data)
} catch {
fatalError("Unable to decode GeoJSON")
}
var restrictionsInfo = [RestrictionInfo]()
for item in geoJson {
if let feature = item as? MKGeoJSONFeature {
let propData = feature.properties!
for geo in feature.geometry {
if geo is MKPolygon {
let polygonInfo = try? JSONDecoder.init().decode(RestrictionInfo.self, from: propData)
restrictionsInfo.append(polygonInfo!)
}
}
}
}
DispatchQueue.main.async {
completion(restrictionsInfo)
}
}
}
}
// Decode GeoJSON from the server
func showRestrictedZones(completion: @escaping ([MKOverlay]) -> ()) {
guard let url = URL(string: "https://flightplan.romatsa.ro/init/static/zone_restrictionate_uav.json") else {
fatalError("Unable to get geoJSON") }
downloadData(fromURL: url) { (returnedData) in
if let data = returnedData {
var geoJson = [MKGeoJSONObject]()
do {
geoJson = try MKGeoJSONDecoder().decode(data)
} catch {
fatalError("Unable to decode GeoJSON")
}
var overlays = [MKOverlay]()
for item in geoJson {
if let feature = item as? MKGeoJSONFeature {
let propData = feature.properties!
for geo in feature.geometry {
if let polygon = geo as? MKPolygon {
let polygonInfo = try? JSONDecoder.init().decode(RestrictionInfo.self, from: propData)
polygon.title = polygonInfo?.zone_id
overlays.append(polygon)
}
}
}
}
DispatchQueue.main.async {
completion(overlays)
}
}
}
}
func downloadData( fromURL url: URL, completion: @escaping (_ data: Data?) -> ()) {
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard
let data = data,
error == nil,
let response = response as? HTTPURLResponse,
response.statusCode >= 200 && response.statusCode < 300 else {
print("Error downloading data.")
completion(nil)
return
}
completion(data)
}
.resume()
}
}
Related Topics
Swift Xcode 7 Beta 5 Type Cannot Refer to Itself as a Requirement
3 Component Dynamic Multi UIpickerview Swift
How to Pause an Animation in Swiftui
Swift "Print" Doesn't Appear in Stdout But 3Rd Party C Library Logs Do When Running in Docker on Ecs
Swiftui Change on Multilevel Children Published Object Change
Swiftui Go Back Programmatically from Representable to View
Applying Impulses in Spritekit
Underlying Type for Tuple in Swift
Drawing at Cocoa with Swift Creates an Error
How Does Swift Disambiguate Type Arguments in Expression Contexts
I Opened My App in Xcode 10 and Now I Have Errors in 9.4.1: Sdkapplicationdelegate (Facebookcore)
How to Calculate The Camera Position from Vuforia Gl Matrix
Do Not Copy Swift Libraries with Xcode 8