Yominim
Yominim

Reputation: 31

How do I make a Button in SwiftUi trigger a function in the UIKit wrapper?

I can add MKPointAnnotations by Long Tap Gesture to my Mapkit MapView.

Now I want to remove those markers by pressing a Button in Swift UI.

My idea was to set a variable true when the button is pressed and use this variable as condition for a function in the updateUIView function. But I get the error message that i can't refer to this variable in this nested function.

Here's a snippet from my addAnnotations wrapper. It works fine. How can I implement the removeAnnotation function on my mapView?

func makeUIView(context: Context) -> MKMapView {
        let map = MKMapView()
        map.setRegion(region, animated: false)
        map.showsUserLocation = true
    
    map.delegate = context.coordinator
    locationManager.delegate = context.coordinator
    
    
    let longPress = UILongPressGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.addAnnotation(gesture:)))
    longPress.minimumPressDuration = 1
    map.addGestureRecognizer(longPress)
    
    
    return map
}


class Coordinator: NSObject, MKMapViewDelegate, CLLocationManagerDelegate {
    
    var mapView: MapView
    init(mapView: MapView) {            
        self.mapView = mapView      
    }
     
    @objc func addAnnotation(gesture: UIGestureRecognizer) {
        
        if gesture.state == .ended {
            
            
            if let mapView = gesture.view as? MKMapView {
                let point = gesture.location(in: mapView)
                let locationCoordinate = mapView.convert(point, toCoordinateFrom: mapView)
                let myPin = MKPointAnnotation()
                myPin.title = "Latitude: \(locationCoordinate.latitude), Longitude: \(locationCoordinate.longitude)"
                myPin.coordinate = locationCoordinate
                mapView.addAnnotation(myPin)
            }
        }
    }

          

Upvotes: 2

Views: 1537

Answers (1)

Denzel
Denzel

Reputation: 323

Firstly, I don't know why you chose to use MapKit with UIKit in a SwiftUI project I assume, when you can do it all in SwiftUI unless you intend to support iOS 13 but anyways here is how you do it:

You have your imports of course:

import SwiftUI
import CoreLocation
import MapKit

Then create a viewModel like so:

class MapViewModel: ObservableObject {
    
    @Published var didPressButton = false
    
}

Add an @StateObject property wrapper(avoid using @ObservevedObject if you are creating the instance in your ContentView)

And yes you can also use @EnvironmentObject

struct ContentView: View {
    
    @StateObject var viewModel = MapViewModel()
    
    var body: some View {
        ZStack {
            MapView(viewModel: viewModel)
            Button("Perform Action") {
            
                viewModel.didPressButton.toggle()
            }
        }
    }
}

Then create an @ObservableObject which will share the same instance of MapViewModel from your ContentView

struct MapView: UIViewRepresentable {

    @ObservableObject var viewModel: MapViewModel

    func makeCoordinator() -> Coordinator {
        return Coordinator()
    }
    
    func updateUIView(_ uiView: MKMapView, context: Context) {
        if viewModel.didPressButton == true {
            context.coordinator.performActionFromSwiftUI()
        }
    }
    
    func makeUIView(context: Context) -> MKMapView {
        let map = context.coordinator.mapView
        map.setRegion(region, animated: false)
        map.showsUserLocation = true
        
        map.delegate = context.coordinator
        locationManager.delegate = context.coordinator
        
        
        let longPress = UILongPressGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.addAnnotation(gesture:)))
        longPress.minimumPressDuration = 1
        map.addGestureRecognizer(longPress)
        
        
        return map
    }
    
}

In your Coordinator class, add the function which will be called in your UIViewRepresentable updateUIView method.

  • I also suggest when returning a mapView in the makeUIView method in your UIViewRepresentable struct, return the context.coordinator.mapView rather than creating an instance there.

class Coordinator: NSObject, MKMapViewDelegate, CLLocationManagerDelegate {
    
    var mapView = MKMapView()
    
    
    func performActionFromSwiftUI() {
        
        print("tapped")
    }
    
    @objc func addAnnotation(gesture: UIGestureRecognizer) {
        
        if gesture.state == .ended {
            
            
            if let mapView = gesture.view as? MKMapView {
                let point = gesture.location(in: mapView)
                let locationCoordinate = mapView.convert(point, toCoordinateFrom: mapView)
                let myPin = MKPointAnnotation()
                myPin.title = "Latitude: \(locationCoordinate.latitude), Longitude: \(locationCoordinate.longitude)"
                myPin.coordinate = locationCoordinate
                mapView.addAnnotation(myPin)
            }
        }
    }
}

Upvotes: 2

Related Questions