Search code examples
iosswiftandroid-mapview

Custom Callout View Button to Map


I am working on a MapView where on click of any custom annotation pin, I am showing custom callout view (load from xib file).

From this Custom Callout I have an UIButton, I already can detect click on this button but I want to access on the Map like the : view?.rightCalloutAccessoryView in the basic callout.

func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView,  {

    if view.annotation!.isKind(of: MKUserLocation.self){
        return
    }

    let customView = (Bundle.main.loadNibNamed("CustomCalloutView", owner: self, options: nil))?[0] as! CustomCalloutView;
    let calloutViewFrame = customView.frame;
    customView.frame = CGRect(x: -calloutViewFrame.size.width/2.23, y: -calloutViewFrame.size.height+10, width: 315, height: 170)

    view.addSubview(customView)

    let region = MKCoordinateRegion(center: pinToZoomOn!.coordinate, span: span)

    mapView.setRegion(region, animated: true)
}

The route is correctly calculated from the classic callout but I can't know how to access my map from the button of my custom callout.

My CustomCalloutViewClass :

import UIKit
import MapKit

class CustomCalloutView: MKAnnotationView {

@IBOutlet weak var goButton: UIButton!

@IBAction func goButton(_ sender: AnyObject) {
    print("Button clicked sucessfully")
}

// MARK: - Detect taps on callout

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    let hitView = super.hitTest(point, with: event)
    if hitView != nil {
        superview?.bringSubview(toFront: self)
    }
    return hitView
}

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    let rect = self.bounds
    var isInside = rect.contains(point)
    if !isInside {
        for view in subviews {
            isInside = view.frame.contains(point)
            if isInside {
                break
            }
        }
    }
    return isInside
}
}

If someone have an idea it will be helpfull I'm stuck on this issue.

Thank you in advance.


Solution

  • Option 1: Capture a MKMapView instance in the closure passed to a CustomCalloutView

    Add the closure which will be called on the button action. The closure will capture the MKMapView instance and you will be able to us is inside.

    class CustomCalloutView: MKAnnotationView {
        var didTapGoButton: (() -> Void)?
    
        @IBAction func goButton(_ sender: AnyObject) {
            didTapGoButton?()
        }
    }
    

    Option 2: Add a weak reference to MKMapView as a property of the callout

    This is not a clean solution but it may be an option under some circumstances. You only have to create a weak property storing a reference to MKMapView instance in CustomCalloutView

    class CustomCalloutView: MKAnnotationView {
        weak var mapView: MKMapView?
    }
    

    Configuration

    This is how you can wire up the CustomCalloutView for both solutions. Remember to use swift capture list to capture a weak reference to a MKMapView instance. Without it you may create a strong reference cycle.

    func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView)  {
        // ...
        let customView = (Bundle.main.loadNibNamed("CustomCalloutView", owner: self, options: nil))?[0] as! CustomCalloutView;
        // Option 1
        customView.didTapGoButton = { [weak mapView ] in 
            print(mapView?.annotations.count) 
        }
        // Option 2
        customView.mapView = mapView
        // ...
    }