swiftui mapkit polygon overlay

1.7k Views Asked by At

I'm trying to show a polygon overlay on the map but I don't find what I'm doing wrong

my MapView file is:

import SwiftUI
import MapKit

struct MapView: UIViewRepresentable {
    
    @EnvironmentObject var vmHome: HomeViewModel
    
    @State var restrictions: [MKOverlay] = []

    func makeCoordinator() -> Coordinator {
        return MapView.Coordinator()
    }
    
    func makeUIView(context: Context) -> MKMapView {
        
        let view = vmHome.mapView
        
        view.showsUserLocation = true
        view.delegate = context.coordinator
        
        vmHome.showRestrictedZones { (restrictions) in
              self.restrictions = restrictions
            print("dentro mapview \(restrictions)")
              view.addOverlays(self.restrictions)
            }
        
        
        return view
    }
    
    func updateUIView(_ uiView: MKMapView, context: Context) {
        
    }
    
    class Coordinator: NSObject,MKMapViewDelegate{
        
        func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
            
            
            if annotation.isKind(of: MKUserLocation.self){return nil}
            else{
                let pinAnnotation = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "PIN_VIEW")
                pinAnnotation.tintColor = .red
                pinAnnotation.animatesDrop = true
                pinAnnotation.canShowCallout = true
                
                return pinAnnotation
            }
}
            
            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)
                     
                    return renderer
                  }
                  return MKOverlayRenderer(overlay: overlay)
                }
        
    }
    
}

then the view model where I want to convert a fixed array of locations in polygon and add them to MKOverlay array (I cut out some come from the view model that is not related to overlay)

import Foundation
import MapKit
import CoreLocation

class HomeViewModel: NSObject, ObservableObject, CLLocationManagerDelegate{
  
    @Published var mapView = MKMapView()
 
    var overlays: [MKOverlay]  = []
    

    
    func showRestrictedZones(completion: @escaping ([MKOverlay]) -> ()) {
        let locations = [CLLocation(latitude: 11.3844028, longitude: 45.6174815), CLLocation(latitude: 11.5608707,longitude: 45.3305094), CLLocation(latitude: 11.8533817, longitude: 45.4447992), CLLocation(latitude: 11.8382755, longitude: 45.6314077), CLLocation(latitude: 11.6624943, longitude: 45.6942722), CLLocation(latitude: 11.3844028, longitude: 45.6174815)]
           var coordinates = locations.map({(location: CLLocation) -> CLLocationCoordinate2D in return location.coordinate})
        
           let polygon = MKPolygon(coordinates: &coordinates, count: locations.count)
       
        
        print(locations.count)
        
        
        
        overlays.append(polygon)
        print(overlays)
        
        DispatchQueue.main.async {
            completion(self.overlays)
                }
        
    }
    
}

ad finally the home view

import SwiftUI
import CoreLocation

struct Home: View {
    @EnvironmentObject var vmHome: HomeViewModel
    
    @State var locationManager = CLLocationManager()
    
    var body: some View {
        VStack{
            HStack{
                Text("Hi,")
                    .font(.title)
                    .foregroundColor(.theme.primary)
                    .padding(.horizontal)
                Spacer()
                
                VStack(alignment: .trailing) {
                    HStack {
                        Image(systemName: "mappin.and.ellipse")
                            .font(.largeTitle)
                            .foregroundColor(.blue)
                        Text("O1")
                            .font(.title)
                            .foregroundColor(.theme.primary)
                    }
                    Text(vmHome.currentAddress)
                        .font(.callout)
                        .foregroundColor(.theme.primary)
                        
                }
                .padding(.horizontal)
            }
            
            ZStack(alignment: .bottom) {
                MapView()
                    .environmentObject(vmHome)
                    .ignoresSafeArea(.all, edges: .bottom)
                
                //VStack{
                    
                    Button(action: vmHome.focusLocation, label: {
                        
                        Image(systemName: "location.fill")
                            .font(.title2)
                            .padding(10)
                            .background(Color.primary)
                            .clipShape(Circle())
                    })
                    .frame(maxWidth: .infinity, alignment: .trailing)
                    .padding()
                    .padding(.bottom)
                    
                //}
                
            }
            
        }
        .background(Color.theme.backgroud)
        .onAppear {
            locationManager.delegate = vmHome
            locationManager.requestWhenInUseAuthorization()
        }
        .alert(isPresented: $vmHome.permissionDenied, content: {
            
            Alert(title: Text("Permission Denied"), message: Text("Please Enable Permission In App Settings"), dismissButton: .default(Text("Goto Settings"), action: {
                
                // Redireting User To Settings...
                UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
            }))
        })
        
        
    }
}

struct Home_Previews: PreviewProvider {
    static var previews: some View {
        Home()
            .environmentObject(HomeViewModel())
    }
}

when I debug the array of MKOverlay I have a value like this [<MKPolygon: 0x282200f30>] so I suppose that inside there's something

thanks

2

There are 2 best solutions below

1
malhal On

I recommend watching WWDC 2019 Integrating SwiftUI to learn the correct design, from 12:41.

Notice their UIViewRepresentable struct has a @Binding var, which in your case should be the array of polygons or overlays. updateView is called when that value changes and that is where you need to update the MKMapView with the differences in the array from last time. Also you should create the MKMapView in makeUIView do not fetch one from somewhere else.

I would also suggest removing the view model objects and instead learning SwiftUI View structs and property wrappers (which make the efficient structs have view model object semantics). WWDC 2019 Data Flow Through SwiftUI is a great starting point. You'll notice they never use objects for view data.

0
multitudes On

In iOS17 SwiftUI supports polygon overlays

It is still in beta so the API might change but if you want to add some polygon overlays to the map the implementation is as follows:

You need to import MapKit obviously and in the SwiftUI view body you add your code starting with Map {}

import MapKit
import SwiftUI

struct ContentView: View {
    var body: some View {
        Map() {
            MapPolygon(coordinates: CLLocationCoordinate2D.locations)
            .stroke(.purple.opacity(0.7), lineWidth: 5)
            .foregroundStyle(.purple.opacity(0.7))
        }
    }
}

The coordinates of the polygon can be defined in various ways. One of them is to add them in an extension for the sake of simplifying the code on StackOverflow, but I think you will get the idea. The difference here is that the MapPolygon View takes an array of CLLocationCoordinate2D instead of CLLocation.

extension CLLocationCoordinate2D {
    static let locations = [
CLLocationCoordinate2D(latitude: 11.3844028, longitude: 45.6174815),
CLLocationCoordinate2D(latitude: 11.5608707, longitude: 45.3305094),
CLLocationCoordinate2D(latitude: 11.8533817, longitude: 45.4447992),
CLLocationCoordinate2D(latitude: 11.8382755, longitude: 45.6314077),
CLLocationCoordinate2D(latitude: 11.6624943, longitude: 45.6942722),
CLLocationCoordinate2D(latitude: 11.3844028, longitude: 45.6174815)]
}

compile and you get this, the Gulf of Eden! :)

enter image description here

I think it is great. Annotations are also very easy to add and they take a SwiftUI view in the body like:

Annotation("Home",coordinate: .home, anchor: .center) {
                ZStack {
                    RoundedRectangle(cornerRadius: 5)
                        .fill(.background)
                    RoundedRectangle(cornerRadius: 5)
                        .stroke(.secondary, lineWidth: 5)
                    Image(systemName: ""mappin.and.ellipse"")
                        .padding(5)
                }
            }

This is just an example. I am not sure what are you trying to code but I understood that you are looking for a way to display polygons. I hope this helps!

see also:
MapPolygon
WWDC23 - Meet MapKit for SwiftUI