Skip To Content ArcGIS for Developers Sign In Dashboard

Overview

You will learn: how to build an app to perform either client-side or server-side SQL and spatial queries to access data.

With the ArcGIS Runtime SDK for iOS you can you can query a feature layer by specifying query parameters. Use these queries to retrieve a sub-set of data for your application. In this scenario, a service feature layer is referenced but not added to the map. Instead, the layer's underlying service feature table is queried using a custom SQL-like whereClause, a geometric feature and a spatial relationship. You will perform a query features function to retrieve data from a service feature table. When features are returned you can add them to your map as graphics or use them to perform other operations in your application.

In this tutorial you will create a query to find all of the "backbone" trails and then add them to the map.

Before you begin

Make sure you have installed the latest version of Xcode.

Reuse the starter project

If you have completed the Create a starter app tutorial, then copy the project into a new empty folder. Otherwise, download and unzip the project solution. Open the .xcodeproj file in Xcode. Run and verify the map displays in the device simulator.

Steps

  1. Create a service feature table as property of ViewController. A service feature table can be constructed with a URL.

    private let trailheadsFeatureTable: AGSServiceFeatureTable = {
        let featureServiceURL = URL(string: "https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/Trails/FeatureServer/0")!
        return AGSServiceFeatureTable(url: featureServiceURL)
    }()
    
  2. Create a graphics overlay as a property of ViewController. A graphics overlay allows you to add spatial graphics to a map that are not backed by a feature table. If you are unfamiliar with graphics overlays, check out the display point, line, and polygon graphics tutorial.

    private let graphicsOverlay = AGSGraphicsOverlay()
    
  3. In setupMap() add the graphic overlay to the map view's graphics overlays array. This will allow any graphics added to the graphics overlay to be rendered by the map view.

    private func setupMap() {
        mapView.map = AGSMap(basemapType: .navigationVector, latitude: 34.09042, longitude: -118.71511, levelOfDetail: 10)
    
        // *** ADD ***
        mapView.graphicsOverlays.add(graphicsOverlay)
    }
    
  4. Reserve the trail name attribute key namespace, this will allow you to build the query as well as interrogate the feature's attributes with the same key. Do this outside the scope of ViewController.

    // *** ADD **
    private extension String {
        static let trailNameKey = "TRL_NAME"
    }
    
  5. Add a function that queries the feature layer with query parameters, set the SQL-like where clause to "TRL_NAME like '%backbone%'" and specify the query to load all feature attributes before returning the results. The query is performed by the service feature table and returns results asynchronously back onto the same thread from which it was called.

    // *** ADD ***
    private func queryFeatureLayer() {
    
        trailheadsFeatureTable.load { [weak self] (error) in
    
            guard let self = self else { return }
    
            if let error = error {
                print("Error loading trailheads feature layer: \(error.localizedDescription)")
                return
            }
    
            let queryParameters = AGSQueryParameters()
            queryParameters.whereClause = "\(String.trailNameKey) like '%backbone%'"
            queryParameters.returnGeometry = true
    
            let outFields: AGSQueryFeatureFields = .loadAll
    
            self.trailheadsFeatureTable.queryFeatures(with: queryParameters, queryFeatureFields: outFields) { (result, error) in
    
                if let error = error {
                    print("Error querying the trailheads feature layer: \(error.localizedDescription)")
                    return
                }
    
                guard let result = result, let features = result.featureEnumerator().allObjects as? [AGSArcGISFeature] else {
                    print("Something went wrong casting the results.")
                    return
                }
    
                self.addFeaturesToMapAsGraphics(features)
            }
        }
    }
    
  6. Add a function that adds graphics for each of the query results' features. This function first builds a simple line symbol that is used to symbolize each graphic and then builds and adds the graphics to the graphics overlay contained by the map view. This function is called after a successful query from the previous step.

    // *** ADD ***
    private func addFeaturesToMapAsGraphics(_ features: [AGSArcGISFeature]) {
    
        let symbol = AGSSimpleLineSymbol(style: .dot,
                                        color: .black,
                                        width: 1.2,
                                        markerStyle: .none,
                                        markerPlacement: .beginAndEnd)
    
        for feature in features {
    
            guard let attributes = feature.attributes as? [String: Any] else { continue }
    
            let graphic = AGSGraphic(geometry: feature.geometry, symbol: symbol, attributes: attributes)
            graphicsOverlay.graphics.add(graphic)
        }
    }
    
  7. Query feature layer in viewDidLoad().

    override func viewDidLoad() {
        super.viewDidLoad()
        setupMap()
    
        // *** ADD ***
        queryFeatureLayer()
    }
    
  8. Press Command-R to run the app in the iOS Simulator. At this point, the map view will show trails with the name "backbone". Now that you have graphics displayed on the map, enable the ability to identify them.

  9. In setupMap() specify ViewController as the map view's touch delegate. This will allow ViewController to respond to tap events.

    private func setupMap() {
        mapView.map = AGSMap(basemapType: .navigationVector, latitude: 34.09042, longitude: -118.71511, levelOfDetail: 10)
    
        mapView.graphicsOverlays.add(graphicsOverlay)
    
        // *** ADD ***
        mapView.touchDelegate = self
    }
    
  10. Adhere ViewController to the AGSGeoViewTouchDelegate function listening for touch at screen point. When the map view detects a tap event, identify graphics where the map view was tapped.

    // *** ADD ***
    extension ViewController: AGSGeoViewTouchDelegate {
    
        func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
    
            // Perform the identify operation with a tolerance of 12 point radius.
            mapView.identify(graphicsOverlay, screenPoint: screenPoint, tolerance: 12, returnPopupsOnly: false) { [weak self] (result) in
    
                guard let self = self else { return }
    
                if let error = result.error {
                    print("Error identifying graphic: \(error.localizedDescription)")
                    self.deselect()
                    return
                }
    
                // Select the graphic, if one is found.
                if let graphic = result.graphics.first {
                    self.select(graphic: graphic, atLocation: mapPoint)
                }
                else {
                    self.deselect()
                }
            }
        }
    }
    

    If the identify operation succeeds, select that graphic and otherwise deselect any previously selected graphics.

  11. Add select and deselect functions. The select function selects the identified graphic and shows the map view's callout. The deselect function deselects any currently selected graphic and hides the map view's callout.

    // *** ADD ***
    private func select(graphic: AGSGraphic, atLocation location: AGSPoint) {
    
        // MARK: Graphics
        graphicsOverlay.clearSelection()
        graphicsOverlay.selectGraphics([graphic])
    
        // MARK: Callout
        let title = graphic.attributes[String.trailNameKey] as? String
        mapView.callout.title = title ?? "Trail"
        mapView.callout.isAccessoryButtonHidden = true
        mapView.callout.show(for: graphic, tapLocation: location, animated: true)
    }
    
    // *** ADD ***
    private func deselect() {
    
        // MARK: Graphics
        graphicsOverlay.clearSelection()
    
        // MARK: Callout
        mapView.callout.isHidden = true
    }
    
  12. Press Command-R to run the app in the iOS Simulator.

Congratulations, you're done!

Verify your project runs. It should display trails as line symbols on the map and allows you to identify trails. Compare your solution with our completed solution project.

Challenge

Experiment with more SQL queries

Try changing the where clause to the ones below and re-run the query task:

LENGTH_MI < 5
LENGTH_MI > 5
ELEV_GAIN < 250
ELEV_GAIN > 250
USE_BIKE = 'Yes'
USE_BIKE = 'No'
USE_HIKE = 'Yes'
USE_EQU = 'Yes'