# Display custom views as callouts

> **Note (legacy): A newer version of the Maps SDK is available**
> 
> This page uses v6.4.1 of the Mapbox Maps SDK. A newer version of the SDK is available. Learn about the latest version, v11.29.0, in the [Maps SDK documentation](https://docs.mapbox.com/ios/ja/maps/examples/simple-map-view/).

![](https://docs.mapbox.com/ios/ja/assets/ideal-img/maps-examples-custom-callout.e46f5ab.480.png)

This example requires multiple files: three for Objective-C, two for Swift.

-   `CustomCalloutView` implements the [`MGLCalloutView`](https://docs.mapbox.com/ios/maps/api/6.4.1/Protocols/MGLCalloutView.html) protocol.
-   `CustomCalloutViewController` uses the custom callout with [`MGLMapView`](https://docs.mapbox.com/ios/maps/api/6.4.1/Classes/MGLMapView.html).

See the [`MGLCalloutView`](https://docs.mapbox.com/ios/maps/api/6.4.1/Protocols/MGLCalloutView.html) and [`MGLCalloutDelegate`](https://docs.mapbox.com/ios/maps/api/6.4.1/Protocols/MGLCalloutViewDelegate.html) protocols for more information about creating customized callouts.

**Swift**

Title: `Header`

```swift
import Mapbox

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 -= 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: UIButton

    required init(representedObject: MGLAnnotation) {
        self.representedObject = representedObject
        self.mainBody = UIButton(type: .system)

        super.init(frame: .zero)

        backgroundColor = .clear

        mainBody.backgroundColor = .darkGray
        mainBody.tintColor = .white
        mainBody.contentEdgeInsets = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)
        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 constrainedRect: CGRect, animated: Bool) {

        delegate?.calloutViewWillAppear?(self)

        view.addSubview(self)

        // Prepare title label.
        mainBody.setTitle(representedObject.title!, for: .normal)
        mainBody.sizeToFit()

        if isCalloutTappable() {
            // Handle taps and eventually try to send them to the delegate (usually the map view).
            mainBody.addTarget(self, action: #selector(CustomCalloutView.calloutTapped), for: .touchUpInside)
        } else {
            // Disable tapping and highlighting.
            mainBody.isUserInteractionEnabled = false
        }

        // 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
        frame = CGRect(x: frameOriginX, y: frameOriginY, width: frameWidth, height: frameHeight)

        if animated {
            alpha = 0

            UIView.animate(withDuration: 0.2) { [weak self] in
                guard let strongSelf = self else {
                    return
                }

                strongSelf.alpha = 1
                strongSelf.delegate?.calloutViewDidAppear?(strongSelf)
            }
        } else {
            delegate?.calloutViewDidAppear?(self)
        }
    }

    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
    }

    @objc 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 = .darkGray

        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()
    }
}
```

**Objective C**

Title: `Header`

```swift
//
//  CustomCalloutView.h
//  Examples
//
//  Created by Jason Wray on 3/6/16.
//  Copyright © 2016 Mapbox. All rights reserved.
//

#import <UIKit/UIKit.h>
@import Mapbox;

@interface CustomCalloutView : UIView <MGLCalloutView>

@end
```

**Swift**

Title: `ViewController`

[View on GitHub](https://github.com/mapbox/ios-sdk-examples/tree/298e050be7352eb28cee6f03e02945593140c1f3/Examples/Swift/CustomCalloutViewExample.swift)

```swift
import Mapbox

class ViewController: UIViewController, MGLMapViewDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()

        let mapView = MGLMapView(frame: view.bounds, styleURL: MGLStyle.lightStyleURL)
        mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        mapView.tintColor = .darkGray
        view.addSubview(mapView)

        // Set the map view‘s delegate property.
        mapView.delegate = self

        // Initialize and add the marker annotation.
        let marker = MGLPointAnnotation()
        marker.coordinate = CLLocationCoordinate2D(latitude: 0, longitude: 0)
        marker.title = "Hello world!"

        // This custom callout example does not implement subtitles.
        //marker.subtitle = "Welcome to my marker"

        // Add marker to the map.
        mapView.addAnnotation(marker)

        // Select the annotation so the callout will appear.
        mapView.selectAnnotation(marker, animated: false, completionHandler: nil)
    }

    func mapView(_ mapView: MGLMapView, annotationCanShowCallout annotation: MGLAnnotation) -> Bool {
        // Only show callouts for `Hello world!` annotation.
        return annotation.responds(to: #selector(getter: MGLAnnotation.title)) && annotation.title! == "Hello world!"
    }

    func mapView(_ mapView: MGLMapView, calloutViewFor annotation: MGLAnnotation) -> MGLCalloutView? {
        // Instantiate and return our custom callout view.
        return CustomCalloutView(representedObject: annotation)
    }

    func mapView(_ mapView: MGLMapView, tapOnCalloutFor annotation: MGLAnnotation) {
        // Optionally handle taps on the callout.
        print("Tapped the callout for: \(annotation)")

        // Hide the callout.
        mapView.deselectAnnotation(annotation, animated: true)
    }
}
```

**Objective C**

Title: `ViewController`

[View on GitHub](https://github.com/mapbox/ios-sdk-examples/tree/298e050be7352eb28cee6f03e02945593140c1f3/Examples/ObjectiveC/CustomCalloutViewExample.m)

```swift
#import "ViewController.h"
#import "CustomCalloutView.h"
@import Mapbox;
@interface ViewController () <MGLMapViewDelegate>
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    MGLMapView *mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds styleURL:[MGLStyle lightStyleURL]];
    mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    mapView.tintColor = [UIColor darkGrayColor];
    [self.view addSubview:mapView];

    // Set the map view‘s delegate property.
    mapView.delegate = self;

    // Initialize and add the marker annotation.
    MGLPointAnnotation *marker = [[MGLPointAnnotation alloc] init];
    marker.coordinate = CLLocationCoordinate2DMake(0, 0);
    marker.title = @"Hello world!";

    // This custom callout example does not implement subtitles.
    //marker.subtitle = @"Welcome to my marker";

    // Add the annotation to the map.
    [mapView addAnnotation:marker];

    // Select the annotation so the callout will appear.
    [mapView selectAnnotation:marker animated:NO completionHandler:nil];
}

- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id <MGLAnnotation>)annotation {
    // Only show callouts for `Hello world!` annotation.
    return [annotation respondsToSelector:@selector(title)] && [annotation.title isEqualToString:@"Hello world!"];
}

- (UIView<MGLCalloutView> *)mapView:(__unused MGLMapView *)mapView calloutViewForAnnotation:(id<MGLAnnotation>)annotation
{
    // Instantiate and return our custom callout view.
    CustomCalloutView *calloutView = [[CustomCalloutView alloc] init];
    calloutView.representedObject = annotation;
    return calloutView;
}

- (void)mapView:(MGLMapView *)mapView tapOnCalloutForAnnotation:(id<MGLAnnotation>)annotation
{
    // Optionally handle taps on the callout.
    NSLog(@"Tapped the callout for: %@", annotation);

    // Hide the callout.
    [mapView deselectAnnotation:annotation animated:YES];
}

@end
```