Back to examples
intermediate

Select a feature within a layer

Allow a user to select a feature within a style layer.

      

import Mapbox

class ViewController: UIViewController, MGLMapViewDelegate, UIGestureRecognizerDelegate {
    
    var mapView: MGLMapView!
    let layerIdentifier = "state-layer"
 
    override func viewDidLoad() {
        super.viewDidLoad()
        
        mapView = MGLMapView(frame: view.bounds)
        mapView.delegate = self
        mapView.setCenter(CLLocationCoordinate2D(latitude: 39.23225, longitude: -97.91015), animated: false)
        mapView.autoresizingMask = [.flexibleHeight, .flexibleWidth]
        view.addSubview(mapView)
        
        // Add a tap gesture recognizer to the map view.
        let gesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
        mapView.addGestureRecognizer(gesture)
    }
    
    func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
        
        // Load a tileset containing U.S. states and their population density. For more information about working with tilesets, see: https://www.mapbox.com/help/studio-manual-tilesets/
        let url = URL(string: "mapbox://examples.69ytlgls")!
        let source = MGLVectorSource(identifier: "state-source", configurationURL: url)
        style.addSource(source)
        
        let layer = MGLFillStyleLayer(identifier: layerIdentifier, source: source)
        
        // Access the tileset layer.
        layer.sourceLayerIdentifier = "stateData_2-dx853g"
        
        // Create a stops dictionary. This defines the relationship between population density and a UIColor.
        let stops = [0: MGLStyleValue(rawValue: UIColor.yellow),
                     600: MGLStyleValue(rawValue: UIColor.red),
                     1200: MGLStyleValue(rawValue: UIColor.blue)]
        
        // Style the fill color using the stops dictionary, exponential interpolation mode, and the feature attribute name.
        layer.fillColor = MGLStyleValue(interpolationMode: .exponential, sourceStops: stops, attributeName: "density", options: [.defaultValue: MGLStyleValue(rawValue: UIColor.white)])
        
        // Insert the new layer below the Mapbox Streets layer that contains state border lines. See the layer reference for more information about layer names: https://www.mapbox.com/vector-tiles/mapbox-streets-v7/
        let symbolLayer = style.layer(withIdentifier: "admin-3-4-boundaries")
        style.insertLayer(layer, below: symbolLayer!)
    }
    
    @objc func handleTap(_ gesture: UITapGestureRecognizer) {
        
        // Get the CGPoint where the user tapped.
        let spot = gesture.location(in: mapView)
        
        // Access the features at that point within the state layer.
        let features = mapView.visibleFeatures(at: spot, styleLayerIdentifiers: Set([layerIdentifier]))
        
        // Get the name of the selected state.
        if let feature = features.first, let state = feature.attribute(forKey: "name") as? String {
            changeOpacity(name: state)
        } else {
            changeOpacity(name: "")
        }
    }
    
    func changeOpacity(name: String) {
        let layer = mapView.style?.layer(withIdentifier: layerIdentifier) as! MGLFillStyleLayer
        
        // Check if a state was selected, then change the opacity of the states that were not selected.
        if name.count > 0 {
            layer.fillOpacity = MGLStyleValue(interpolationMode: .categorical, sourceStops: [name: MGLStyleValue<NSNumber>(rawValue: 1)], attributeName: "name", options: [.defaultValue: MGLStyleValue<NSNumber>(rawValue: 0)])
        } else {
            // Reset the opacity for all states if the user did not tap on a state.
            layer.fillOpacity = MGLStyleValue(rawValue: 1)
        }
    }
}




      
      


#import "ViewController.h"
@import Mapbox;

@interface ViewController () <MGLMapViewDelegate>

@property (nonatomic) MGLMapView *mapView;
@property (nonatomic) NSString *layerIdentifier;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.mapView = [[MGLMapView alloc] initWithFrame:self.view.bounds];
    self.mapView.delegate = self;
    [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(39.23225, -97.91015)];
    
    self.mapView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
    [self.view addSubview:self.mapView];

    // Store the name of the style layer in which states will be drawn.
    self.layerIdentifier = @"state-layer";
    
    // Add a tap gesture recognizer to the map view.
    UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
    gesture.numberOfTapsRequired = 1;
    [self.mapView addGestureRecognizer:gesture];
}

- (void)mapView:(MGLMapView *)mapView didFinishLoadingStyle:(MGLStyle *)style {
    
    // Load a tileset containing U.S. states and their population density. For more information about working with tilesets, see: https://www.mapbox.com/help/studio-manual-tilesets/
    NSURL *url = [NSURL URLWithString:@"mapbox://examples.69ytlgls"];
    
    MGLVectorSource *source = [[MGLVectorSource alloc] initWithIdentifier:@"state-source" configurationURL:url];
    [style addSource:source];

    MGLFillStyleLayer *layer = [[MGLFillStyleLayer alloc] initWithIdentifier:self.layerIdentifier source:source];
    
    // Access the tileset layer.
    layer.sourceLayerIdentifier = @"stateData_2-dx853g";
    
    // Create a stops dictionary. This defines the relationship between population density and a UIColor.
    NSDictionary *stops = @{
            @0: [MGLStyleValue valueWithRawValue:[UIColor yellowColor]],
            @600: [MGLStyleValue valueWithRawValue:[UIColor redColor]],
            @1200: [MGLStyleValue valueWithRawValue:[UIColor blueColor]]
        };
    
    // Style the fill color using the stops dictionary, exponential interpolation mode, and the feature attribute name.
    layer.fillColor = [MGLStyleValue valueWithInterpolationMode:MGLInterpolationModeExponential
            sourceStops:stops
            attributeName:@"density"
            options:@{ MGLStyleFunctionOptionDefaultValue: [MGLStyleValue valueWithRawValue:[UIColor whiteColor]] }];
    
    // Insert the new layer below the Mapbox Streets layer that contains state border lines. See the layer reference for more information about layer names: https://www.mapbox.com/vector-tiles/mapbox-streets-v7/
    MGLStyleLayer *symbolLayer = [style layerWithIdentifier:@"admin-3-4-boundaries"];
    
    [style insertLayer:layer belowLayer:symbolLayer];
}

- (void)handleTap:(UITapGestureRecognizer *)gesture {
    
    // Get the CGPoint where the user tapped.
    CGPoint spot = [gesture locationInView:self.mapView];
    
    // Access the features at that point within the state layer.
    NSArray *features = [self.mapView visibleFeaturesAtPoint:spot
                                inStyleLayersWithIdentifiers:[NSSet setWithObject:self.layerIdentifier]];
    
    MGLPolygonFeature *feature = features.firstObject;
    
    // Get the name of the selected state.
    NSString *state = [feature attributeForKey:@"name"];
    
    [self changeOpacityBasedOn:state];
}

- (void)changeOpacityBasedOn:(NSString*)name {
    MGLFillStyleLayer *layer = (MGLFillStyleLayer *)[self.mapView.style layerWithIdentifier:self.layerIdentifier];
    
    // Check if a state was selected, then change the opacity of the states that were not selected.
    if (name.length > 0) {
        layer.fillOpacity = [MGLStyleValue valueWithInterpolationMode:MGLInterpolationModeCategorical
                sourceStops:@{ name: [MGLStyleValue valueWithRawValue:@1] }
                attributeName:@"name"
                options:@{ MGLStyleFunctionOptionDefaultValue: [MGLStyleValue valueWithRawValue:@0] }];
    } else {
        // Reset the opacity for all states if the user did not tap on a state.
        layer.fillOpacity = [MGLStyleValue valueWithRawValue:@1];
    }
}

@end