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

Set the app settings

  1. Add a new swift file to your Xcode project named AppConfiguration. You will use this to hold configuration constants required by your app.

  2. Add the following extensions to AppConfiguration.swift. The trail name key will be used to format the query parameters' where clause. The trailheads URL is used to construct a service feature table.

    /** ADD **/
    extension String {
        static let trailNameKey = "TRL_NAME"
    }
    
    extension URL {
        static let trailheads = URL(string: "https://services3.arcgis.com/GVgbJbqm8hXASVYi/arcgis/rest/services/Trails/FeatureServer/0")!
    }
    
  3. 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()
    
  4. 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)
}
  1. Construct a service feature table using the trailheads URL.

    private let trailheadsFeatureTable: AGSServiceFeatureTable = {
        // Build service feature table from feature service URL.
        let trailheads = AGSServiceFeatureTable(url: .trailheads)
        trailheads.load(completion: nil)
        return trailheads
    }()
    
  2. 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 }
    
            // Return early if the load produced an error.
            if let error = error {
                print(error.localizedDescription)
                return
            }
    
            // Build query parameters.
            let queryParameters = AGSQueryParameters()
            queryParameters.whereClause = "\(String.trailNameKey) like '%Backbone Trail -%'"
            queryParameters.returnGeometry = true
    
            // Specify load all attributes.
            let outFields: AGSQueryFeatureFields = .loadAll
    
            // Query the feature table.
            self.trailheadsFeatureTable.queryFeatures(with: queryParameters, queryFeatureFields: outFields) { [weak self] (result, error) in
                guard let self = self else { return }
    
                if let result = result, let features = result.featureEnumerator().allObjects as? [AGSArcGISFeature] {
                    self.addFeaturesToMapAsGraphics(features)
                }
                else if let error = error {
                    print(error.localizedDescription)
                }
            }
        }
    }
    
  3. 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]) {
    
        // Create a symbol that will be used to symbolize each graphic.
        let symbol = AGSSimpleLineSymbol(
            style: .dot,
            color: .black,
            width: 1.2,
            markerStyle: .none,
            markerPlacement: .beginAndEnd
        )
    
        // Create a graphic from each feature and add the graphic to the graphics overlay.
        let graphics = features.compactMap { (feature) -> AGSGraphic? in
            // Ensure the feature contains an attributes dictionary.
            guard let attributes = feature.attributes as? [String: Any] else { return nil }
    
            return AGSGraphic(
                geometry: feature.geometry,
                symbol: symbol,
                attributes: attributes
            )
        }
    
        graphicsOverlay.graphics.addObjects(from: graphics)
    }
    
  4. Query feature layer in viewDidLoad().

    override func viewDidLoad() {
        super.viewDidLoad()
        setupMap()
    
        // *** ADD ***
        queryFeatureLayer()
    }
    
  5. Press Command-R to run the app in the iOS Simulator.

    (Note, as of 100.8 Runtime supports Metal. In order to run your app in a simulator you must meet some minimum requirements. You must be developing on macOS Catalina, using Xcode 11, and simulating iOS 13.)

  6. 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
    }
    
  7. Adopt ViewController to the AGSGeoViewTouchDelegate protocol and listen for touch at screen point events. 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) {
    
            deselect()
    
            // 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 }
    
                // Select the graphic, if one is found.
                if let graphic = result.graphics.first {
                    self.select(graphic: graphic, atLocation: mapPoint)
                }
                else if let error = result.error {
                    print(error.localizedDescription)
                    return
                }
            }
        }
    }
    

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

  8. 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) {
    
        // Clear previous selection.
        graphicsOverlay.clearSelection()
    
        // Select the newly queried graphic.
        graphic.isSelected = true
    
        // Grab the trail name from the attributes dictionary.
        let title = graphic.attributes[String.trailNameKey] as? String
    
        // Configure and show the graphic in a callout.
        mapView.callout.title = title ?? "Trail"
        mapView.callout.isAccessoryButtonHidden = true
        mapView.callout.show(for: graphic, tapLocation: location, animated: true)
    }
    
    private func deselect() {
    
        // Clear any graphics selection, if there is one.
        graphicsOverlay.clearSelection()
    
        // Hide the callout.
        mapView.callout.isHidden = true
    }
    
  9. Press Command-R to run the app in the iOS Simulator.

    (Note, as of 100.8 Runtime supports Metal. In order to run your app in a simulator you must meet some minimum requirements. You must be developing on macOS Catalina, using Xcode 11, and simulating iOS 13.)

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'