0

Using Mapbox, the map struct conforms to the UIViewRepresentable protocol. In my makeUIView() function, I create a tap gesture recognizer and add it to the map view.

struct Map: UIViewRepresentable {

    private let mapView: MGLMapView = MGLMapView(frame: .zero, styleURL: MGLStyle.streetsStyleURL)

    func makeUIView(context: UIViewRepresentableContext<Map>) -> MGLMapView {
        mapView.delegate = context.coordinator
        let gestureRecognizer = UITapGestureRecognizer(target: context.coordinator, action: Selector("tappedMap"))
        mapView.addGestureRecognizer(gestureRecognizer)
        return mapView
    }

    func updateUIView(_ uiView: MGLMapView, context: UIViewRepresentableContext<Map>) {
        print("Just updated the mapView")
    }

    func makeCoordinator() -> Map.Coordinator {
        Coordinator(appState: appState, self)
    }

    // other functions that make the struct have functions required by Mapbox 

    func makeCoordinator() -> Map.Coordinator {
        Coordinator(self)
    }

    final class Coordinator: NSObject, MGLMapViewDelegate {
        var control: Map

        //a bunch of delegate functions

        @objc func tappedMap(sender: UITapGestureRecognizer) {
            let locationInMap = sender.location(in: control)
            let coordinateSet = sender.convert(locationInMap, toCoordinateFrom: control)
        }
    }
}

Neither of the lines in the tappedMap function compile properly...also, when I have 'sender: UITapGestureRecognizer' in the parameters of tappedMap, I causes the application to crash when I tap the mapView--If I remove the parameter, then the function is at least called properly without crashing. Please help

Gene Z. Ragan
  • 2,643
  • 2
  • 31
  • 41
nickcoding
  • 305
  • 8
  • 35
  • 1
    What error/stack trace do you get from the crash? I don't know Mapbox, but I would start with a breakpoint inside `tappedMap` and inspect the object being received there. – John Nimis Feb 19 '21 at 13:21
  • @JohnNimis I don't know where the stack trace is in Xcode...sorry kind of a newbie here--the error that is thrown says Thread 1: ........: unrecognizable selector sent to instance and then that's followed with the memory address I think. That leads me to believe that it's not receiving a UITapGestureRecognizer but something else, is that correct? Also if I set a breakpoint on even the first line of the method (when I make the first line a print statement) the error is still thrown so it's something with what the tappedMap function is receiving or how it's being called – nickcoding Feb 19 '21 at 13:35
  • 1
    OK, I'm trying to reproduce the error, but I'm getting a bunch of compiler errors: one is "Class 'Map.Coordinator' has no initializers", and the other have to do with the `mapView` property, which isn't declared in your code sample. Also, are you importing MapKit in any of these files? You might consider naming your `Map` struct differently, in case there is some kind of namespace collision. – John Nimis Feb 21 '21 at 14:05
  • This isn't MapKit, this is MapBox, and you need to have a pod file that imports the correct cocoapods – nickcoding Feb 21 '21 at 14:08
  • 1
    Yes, I'm aware, and I've imported Mapbox. I asked because MapKit also defines a struct called `Map`. I only noticed this because my sample project already had some MapKit code in a different file. – John Nimis Feb 21 '21 at 14:11
  • Updated my post to include all the stuff I think you need--Also tried to change the struct name and that had no effect – nickcoding Feb 21 '21 at 14:18

1 Answers1

1

OK, the first problem is your selector definition in the tapGesture declaration. Change it to this:

let gestureRecognizer = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.tappedMap(sender:)))

Also, you need to manage the priority of gesture recognizers, since MGLMapView includes UIGestureRecognizer logic internally. I found a discussion of here:

Adding your own gesture recognizer to MGLMapView will block the corresponding gesture recognizer built into MGLMapView. To avoid conflicts, define which gesture takes precedence.

You can try this code in your project (pretty much just copied from the linked page above) EDIT: I needed to change the order of lines from my original answer:

let gestureRecognizer = UITapGestureRecognizer(target: context.coordinator, action: Selector("tappedMap"))

// HERE'S THE NEW STUFF:
for recognizer in mapView.gestureRecognizers! where recognizer is UITapGestureRecognizer {
    gestureRecognizer.require(toFail: recognizer)
}

mapView.addGestureRecognizer(gestureRecognizer)

EDIT: I was able to test this.

I'm a little confused by the logic inside your tappedMap selector: maybe you meant something like this?

@objc func tappedMap(sender: UITapGestureRecognizer) {
    let locationInMap = sender.location(in: control.mapView)
    let coordinateSet = sender.view?.convert(locationInMap, to: control.mapView)
}
John Nimis
  • 586
  • 3
  • 15
  • That is what I meant for the tappedMap function I think (I'm iffy on @objc functions in general though). When I tap on the mapView though, I get a thread error that says 'unrecognized selector sent to instance'. I'm also about to update my original answer because I think you also need to include the updateUIView() function to get it to compile... – nickcoding Feb 21 '21 at 17:53
  • 1
    @nickcoding I updated my answer, and was able to verify that it fixes the issue – John Nimis Feb 21 '21 at 18:13
  • It detects where the tap was on the screen but I was trying to imitate the top answer here: https://stackoverflow.com/questions/34431459/ios-swift-how-to-add-pinpoint-to-map-on-touch-and-get-detailed-address-of-th where the person taps on the view and that tap is converted to latitude and longitude of where the tap was--I thought that's what the .convert() function did but I think the way you set coordinateSet just converts the position on screen in one view to the same position in the contained view – nickcoding Feb 21 '21 at 18:22
  • 1
    Ah, I see! That's pretty cool. In that case, the second line should be: `let coordinateSet = control.mapView.convert(locationInMap, toCoordinateFrom: control.mapView)` – John Nimis Feb 21 '21 at 18:34
  • This worked like a charm, thank you so much! (I'll award the bounty when I can) – nickcoding Feb 21 '21 at 18:49