Proper positioning of Mapbox Callout view on top

494 views Asked by At

I would like to make the callout box position on top of the location marker which is the black one on the upper left of the box.

Here is what my code currently is:

import Mapbox
import UIKit

class CustomCalloutView: UIView, MGLCalloutView {
var representedObject: MGLAnnotation

// Allow the callout to remain open during panning.
let dismissesAutomatically: Bool = false
let isAnchoredToAnnotation: Bool = true

// https://github.com/mapbox/mapbox-gl-native/issues/9228
override var center: CGPoint {
    set {
        var newCenter = newValue
        newCenter.y = newCenter.y - bounds.midY
        super.center = newCenter
    }
    get {
        return super.center
    }
}

lazy var leftAccessoryView = UIView() /* unused */
lazy var rightAccessoryView = UIView() /* unused */

weak var delegate: MGLCalloutViewDelegate?

let tipHeight: CGFloat = 10.0
let tipWidth: CGFloat = 20.0

let mainBody: UIView

required init(representedObject: MGLAnnotation) {
    self.representedObject = representedObject
    self.mainBody = CustomDetailsView().loadNib()

    super.init(frame: .zero)

    backgroundColor = .clear
    autoresizesSubviews = true
    mainBody.backgroundColor = .white
    mainBody.layer.cornerRadius = 4.0

    addSubview(mainBody)
}

required init?(coder decoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}


// MARK: - MGLCalloutView API
func presentCallout(from rect: CGRect, in view: UIView, constrainedTo constrainedView: UIView, animated: Bool) {
    if !representedObject.responds(to: #selector(getter: MGLAnnotation.title)) {
        return
    }

    view.addSubview(self)
    mainBody.sizeToFit()

    // Prepare our frame, adding extra space at the bottom for the tip
    let button = UIButton(type: .system)
    let frameWidth = button.bounds.size.width
    let frameHeight = button.bounds.size.height + tipHeight
    let frameOriginX = rect.origin.x + (rect.size.width/2.0) - (frameWidth/2.0)
    let frameOriginY = rect.origin.y - frameHeight
    frame = CGRect(x: frameOriginX, y: frameOriginY, width: frameWidth, height: frameHeight)

    if animated {
        alpha = 0

        UIView.animate(withDuration: 0.2) { [weak self] in
            self?.alpha = 1
        }
    }
}

func dismissCallout(animated: Bool) {
    if (superview != nil) {
        if animated {
            UIView.animate(withDuration: 0.2, animations: { [weak self] in
                self?.alpha = 0
                }, completion: { [weak self] _ in
                    self?.removeFromSuperview()
            })
        } else {
            removeFromSuperview()
        }
    }
}

// MARK: - Callout interaction handlers

func isCalloutTappable() -> Bool {
    if let delegate = delegate {
        if delegate.responds(to: #selector(MGLCalloutViewDelegate.calloutViewShouldHighlight)) {
            return delegate.calloutViewShouldHighlight!(self)
        }
    }
    return false
}

func calloutTapped() {
    if isCalloutTappable() && delegate!.responds(to: #selector(MGLCalloutViewDelegate.calloutViewTapped)) {
        delegate!.calloutViewTapped!(self)
    }
}

// MARK: - Custom view styling

override func draw(_ rect: CGRect) {
    // Draw the pointed tip at the bottom
    let fillColor : UIColor = .white

    let tipLeft = rect.origin.x + (rect.size.width / 2.0) - (tipWidth / 2.0)
    let tipBottom = CGPoint(x: rect.origin.x + (rect.size.width / 2.0), y: rect.origin.y + rect.size.height)
    let heightWithoutTip = rect.size.height - tipHeight - 1

    let currentContext = UIGraphicsGetCurrentContext()!

    let tipPath = CGMutablePath()
    tipPath.move(to: CGPoint(x: tipLeft, y: heightWithoutTip))
    tipPath.addLine(to: CGPoint(x: tipBottom.x, y: tipBottom.y))
    tipPath.addLine(to: CGPoint(x: tipLeft + tipWidth, y: heightWithoutTip))
    tipPath.closeSubpath()

    fillColor.setFill()
    currentContext.addPath(tipPath)
    currentContext.fillPath()
}

}

enter image description here

I couldn't seem to find a way to get the callout to position on top of the pin. Is there a way to achieve the same result with this current code setup?

1

There are 1 answers

0
Jojo Narte On

I ended up having to do a bit of change to the way the mainBody.frame was positioned using snippet below:

// Prepare our frame, adding extra space at the bottom for the tip
        let frameWidth = mainBody.bounds.size.width
        let frameHeight = mainBody.bounds.size.height + tipHeight
        let frameOriginX = rect.origin.x + (rect.size.width/2.0) - (frameWidth/2.0)
        let frameOriginY = rect.origin.y - frameHeight
        self.frame = CGRect(x: frameOriginX, y: frameOriginY, width: frameWidth, height: frameHeight)
        mainBody.frame.size.width = frameWidth
        mainBody.frame.size.height = frameHeight - tipHeight