Custom Annotation Callout Views

In addition to just being able to customize the callout text for map annotations associated with weather layers, you can take this a step further and provide an entirely custom view to display in the callout instead of the standard title and subtitle. This allows you to display datasets and visuals in a unique way for your particular application beyond just text.

For example, annotation callouts for the AWFMapLayerTropical point data layer takes advantage of this feature. You’ll noticed that the default callout view for a tropical cyclone’s annotation isn’t the standard title/subtitle text but provides far more information in a custom view:

Tropical Position Callout View

To accomplish this, you’ll need to assign a data source to your AWFWeatherMap instance using the dataSource property. Then, the data source should implement the weatherMap:calloutViewForAnnotation: method and return the necessary view for the particular annotation instance that’s provided. If you don’t want to use a custom view for the annotation, just return nil from this data source method.

The following example is how the custom view for tropical cyclone position annotations is implemented in AWFWeatherMapViewController, which is the data source of the controller’s weatherMap instance. A custom UIView subclass, TropicalCyclonePositionCalloutView is used for the callout’s content view:

ObjectiveC
Swift
@interface TropicalCyclonePositionCalloutView : UIView
- (void)configure:(AWFTropicalCyclonePosition *)data;
@end

@implementation TropicalCyclonePositionCalloutView

- (void)configure:(AWFTropicalCyclonePosition *)data {
    // configure the view with the model data...
}

@end
#pragma mark - AWFWeatherMapDataSource

- (UIView *)weatherMap:(AWFWeatherMap *)weatherMap calloutViewForAnnotation:(id<AWFAnnotation>)annotation {
    // since we need to access the model object associated with the annotation, we need 
    // to do a type check on the annoation to make sure it conforms to the AWFStyledMapItem protocol first
    if ([annotation conformsToProtocol:@protocol(AWFStyledMapItem)]) {
        id<AWFStyledMapItem> styledAnnotation = (id<AWFStyledMapItem>)annotation;
        
        // we only want to use a custom view for map annotations for tropical cyclone position points, so
        // make sure the model object associated with the annotation is an instance of AWFTropicalCyclonePosition
        if ([styledAnnotation.modelObject isKindOfClass:[AWFTropicalCyclonePosition class]]) {
        
            // create an instance of the view we want to display in the annotation's callout
            TropicalCyclonePositionCalloutView *view = [TropicalCyclonePositionCalloutView new];
            
            // configure the view for the annotation's model object
            [view configure:(AWFTropicalCyclonePosition *)styledAnnotation.modelObject];
            
            // return the view so it's used by the weather map
            return view;
        }
    }
    
    // no custom view for the annotation, so use the default by returning `nil`
    return nil;
}
class TropicalCyclonePositionCalloutView: UIView {

    func configure(data: AWFTropicalCyclonePosition) {
        // configure the view with the model data...
    }
}

extension MapViewController: AWFWeatherMapDataSource {
    
    func weatherMap(_ weatherMap: AWFWeatherMap, calloutViewFor annotation: AWFAnnotation) -> UIView? {
        // since we need to access the model object associated with the annotation, we need 
        // to do a type check on the annoation to make sure it conforms to the AWFStyledMapItem protocol first, 
        // then check that the model object associated with the annotation is an instance of the type
        // class we want to use the custom callout view for
        if let styledAnnotation = annotation as? AWFStyledMapItem, let model = styledAnnotation.modelObject as? AWFTropicalCyclonePosition {
        
            // create an instance of the view we want to display in the annotation's callout
            let view = TropicalCyclonePositionCalloutView()
            
            // configure the view for the annotation's model object
            view.configure(data: model)
            
            // return the view so it's used by the weather map
            return view
        }
        
        // no custom view for the annotation, so use the default by returning `nil`
        return nil
    }
}

Last modified: October 12, 2018