Search code examples
swiftannotationsmapboxuipopovercontroller

How to set MGLAnnotationView as sourceView for popover


What I want to do is present a popover with the annotation as its source view/anchor

By using delegate function didSelect annotationView I should be able to achieve this, but it doesn't seem to run at all. For now I'm just presenting my popover via didSelect annotation and have set the sourceView to nav bar, just for showing it somewhere...

FYI: I have implemented the Mapbox SDK into the project. I have no problems performing the same task using MapKit.

Does anyone have any idea on what I can do to achieve this?

code snippets below:

import UIKit
import Mapbox

class ViewController: UIViewController, MGLMapViewDelegate, UIPopoverPresentationControllerDelegate {

@IBOutlet var theMap: MGLMapView!

override func viewDidLoad() {
    super.viewDidLoad()

    theMap.delegate = self

    let point = MGLPointAnnotation()
    point.coordinate = CLLocationCoordinate2D(latitude: 55.6, longitude: 13.0)
    point.title = "Some place"
    point.subtitle = "Malmö, Sweden"
    theMap.addAnnotation(point)
}

func mapView(_ mapView: MGLMapView, didSelect annotationView: MGLAnnotationView) {
    print("annotation view: ", annotationView)
    // this method doesn't seem to get called at all...
    // but ideally this is the place to present the popover.
}

func mapView(_ mapView: MGLMapView, didSelect annotation: MGLAnnotation) {
    print("annotation: ", annotation)
    // present the popover
    presentPopover()
}

func presentPopover(){
    let popover = storyboard?.instantiateViewController(withIdentifier: "MyCalloutVC") as! MyCallout

    popover.modalPresentationStyle = UIModalPresentationStyle.popover
    popover.popoverPresentationController?.backgroundColor = UIColor.white
    popover.popoverPresentationController?.delegate = self

    // I would like to set the source anchor to the selected annotation view.
    popover.popoverPresentationController?.sourceView = UINavigationBar() // set to nav bar for now...
    popover.popoverPresentationController?.permittedArrowDirections = .any
    // popover size set in MyCallout 
    self.present(popover, animated: true)
    }
}

Solution

  • The function does not get called because there is no annotation view, which can be selected. It means that you simply add an point annotation and not an annotation view. Therefore you will have to do this: When you add the annotation the func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {} gets called, where you can define an annotation view an return it. Then the did selected function will be called. Have a look at the example code : https://www.mapbox.com/ios-sdk/examples/annotation-views/

    import Mapbox
    
    // Example view controller
    class ViewController: UIViewController, MGLMapViewDelegate {
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let mapView = MGLMapView(frame: view.bounds)
            mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
            mapView.styleURL = MGLStyle.darkStyleURL(withVersion: 9)
            mapView.tintColor = .lightGray
            mapView.centerCoordinate = CLLocationCoordinate2D(latitude: 0, longitude: 66)
            mapView.zoomLevel = 2
            mapView.delegate = self
            view.addSubview(mapView)
    
            // Specify coordinates for our annotations.
            let coordinates = [
                CLLocationCoordinate2D(latitude: 0, longitude: 33),
                CLLocationCoordinate2D(latitude: 0, longitude: 66),
                CLLocationCoordinate2D(latitude: 0, longitude: 99),
            ]
    
            // Fill an array with point annotations and add it to the map.
            var pointAnnotations = [MGLPointAnnotation]()
            for coordinate in coordinates {
                let point = MGLPointAnnotation()
                point.coordinate = coordinate
                point.title = "\(coordinate.latitude), \(coordinate.longitude)"
                pointAnnotations.append(point)
            }
    
            mapView.addAnnotations(pointAnnotations)
        }
    
        // MARK: - MGLMapViewDelegate methods
    
        // This delegate method is where you tell the map to load a view for a specific annotation. To load a static MGLAnnotationImage, you would use `-mapView:imageForAnnotation:`.
        func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
            // This example is only concerned with point annotations.
            guard annotation is MGLPointAnnotation else {
                return nil
            }
    
            // Use the point annotation’s longitude value (as a string) as the reuse identifier for its view.
            let reuseIdentifier = "\(annotation.coordinate.longitude)"
    
            // For better performance, always try to reuse existing annotations.
            var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier)
    
            // If there’s no reusable annotation view available, initialize a new one.
            if annotationView == nil {
                annotationView = CustomAnnotationView(reuseIdentifier: reuseIdentifier)
                annotationView!.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
    
                // Set the annotation view’s background color to a value determined by its longitude.
                let hue = CGFloat(annotation.coordinate.longitude) / 100
                annotationView!.backgroundColor = UIColor(hue: hue, saturation: 0.5, brightness: 1, alpha: 1)
            }
    
            return annotationView
        }
    
        func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
            return true
        }
    }
    
    //
    // MGLAnnotationView subclass
    class CustomAnnotationView: MGLAnnotationView {
        override func layoutSubviews() {
            super.layoutSubviews()
    
            // Force the annotation view to maintain a constant size when the map is tilted.
            scalesWithViewingDistance = false
    
            // Use CALayer’s corner radius to turn this view into a circle.
            layer.cornerRadius = frame.width / 2
            layer.borderWidth = 2
            layer.borderColor = UIColor.white.cgColor
        }
    
        override func setSelected(_ selected: Bool, animated: Bool) {
            super.setSelected(selected, animated: animated)
    
            // Animate the border width in/out, creating an iris effect.
            let animation = CABasicAnimation(keyPath: "borderWidth")
            animation.duration = 0.1
            layer.borderWidth = selected ? frame.width / 4 : 2
            layer.add(animation, forKey: "borderWidth")
        }
    }