How to determine if AGSFeature is selected

696
8
12-18-2023 07:42 AM
TravisYordi
New Contributor II

I have a use case where I need to be able to toggle whether a single feature is selected or not.  To do that, I need to be able to determine if it already selected, but I cannot seem to find anywhere in the code or documentation a quick/easy way such as a property on AGSFeature (doesn't exist).  Reaching out to see if there is a way outside of gathering all selected features and checking if the feature I tapped is included. That route will be too slow as there may be quite a few already selected features.  Any help would be appreciated, thanks.

0 Kudos
8 Replies
Ting
by Esri Contributor
Esri Contributor

Thanks for reaching out. You may use the getSelectedFeaturesWithCompletion method to get an iterator of all selected features in the feature layer, and then find if the feature is in there or not.

 

featureLayer.getSelectedFeatures { result, error in
    if let result {
        let selectedFeatures = result.featureEnumerator()
        let isSelected = selectedFeatures.contains { ($0 as? AGSFeature) == /* someFeature */ }
    }
}

 

 The alternative way, as you mentioned, is to maintain a collection (Set) of all selected features locally, and check if a feature is selected, or add/remove the features when select/unselect.

Hope it helps. Please let me know if you have other questions.

0 Kudos
TravisYordi
New Contributor II

Thanks for the reply Ting.  The solution you provided is what I was trying to avoid. 'result.featureEnumerator().allObjects" will load all of those selected features into memory, and if we're talking about thousands or 10s of thousands of selected features (very possible in our scenario) the device will run out of memory pretty quick.  What I was looking for was some sort of "isSelected" property that I could get from the geoElements that get returned from "mapView.identifyLayer".

0 Kudos
Ting
by Esri Contributor
Esri Contributor

My mistake - I've edited the code a bit. To find a feature, you don't need to load all the features into memory using allObjects. Loop through the iterator would only consume the memory for 1 object at a time.

In terms of why a feature doesn't have isSelected property, I think it might come down to the fact that a feature can be both offline (e.g., from a shapefile feature table) and online (e.g. from a service feature table). It is not easy to store the selection state on the feature object itself. If a user pans away from a selected feature, it may also be removed from memory in order to query and draw the new features and keep the memory footprint low.

This is not a definitive answer though, and I'll ask our team to see if other options have been considered in terms of the isSelected property on a feature.

0 Kudos
TravisYordi
New Contributor II

mapView.identifyLayer(layer, screenPoint: screenPoint, tolerance: 20, returnPopupsOnly: false) { result in layer.getSelectedFeatures { selectedResults, _ in let results: [Bool] = result.geoElements.compactMap { geoElement in selectedResults?.featureEnumerator().contains(where: { ($0 as? AGSFeature) == (geoElement as! AGSFeature) }) ?? false } } }

I ran this test based on your suggestion with 25 selected features on my map.  Unfortunately the results object is empty in my test. The feature that I tap on (geoElement) is selected on my map, but the selectedResults.featureEnumerator() doesn't find any equality with that object (both cast to AGSFeature).

0 Kudos
Ting
by Esri Contributor
Esri Contributor

I may have mislead you in the previous reply. By looping through the iterator, each element is instantiated on the fly, so comparing it against the geo element held in the memory won't work. Consider this snippet - as soon as the feature is selected, test and see if it can be retrieved with the getSelectedFeaturesWithCompletion method.

 

import UIKit
import ArcGIS

class ViewController: UIViewController {
    @IBOutlet var mapView: AGSMapView! {
        didSet {
            let map = AGSMap(basemapStyle: .arcGISLightGray)
            map.operationalLayers.add(featureLayer)
            mapView.map = map
            mapView.setViewpointCenter(AGSPointMakeWGS84(34.56, -118.37), scale: 4e5)
            mapView.touchDelegate = self
        }
    }
    
    let featureLayer = AGSFeatureLayer(
        featureTable: AGSServiceFeatureTable(
            url: URL(string: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/Energy/Geology/FeatureServer/9")!
        )
    )
    
    var selectedFeature: AGSArcGISFeature?
}

extension ViewController: AGSGeoViewTouchDelegate {
    func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
        featureLayer.clearSelection()
        
        geoView.identifyLayer(
            featureLayer,
            screenPoint: screenPoint,
            tolerance: 10,
            returnPopupsOnly: false
        ) { [unowned self] result in
            if let feature = result.geoElements.first as? AGSArcGISFeature {
                featureLayer.select(feature)
                selectedFeature = feature
            } else if let error = result.error {
                print(error)
            }
            
            // Test if the selected feature can be retrieved.
            featureLayer.getSelectedFeatures { result, _ in
                if let result {
                    let isSelected = result.featureEnumerator().contains {
                        self.objectID(of: $0 as? AGSArcGISFeature) == self.objectID(of: self.selectedFeature)
                    }
                    print(isSelected)
                }
            }
        }
    }
    
    func objectID(of feature: AGSArcGISFeature?) -> String? {
        feature?.attributes["OBJECTID"] as? String
    }
}

 

 

0 Kudos
TravisYordi
New Contributor II

var selectedFeatures: Set = [] func selectFeaturesFromSelection(layerName: String, completion: (() -> Void)? = nil) { if let layer = (map.operationalLayers as? [AGSFeatureLayer])?.first(where: { $0.name == layerName }), let selectionGeometry = sketchEditor.geometry { let params = AGSQueryParameters() params.geometry = selectionGeometry layer.selectFeatures(withQuery: params, mode: .add) { [unowned self] result, error in if let result = result { for case let feature as AGSFeature in result.featureEnumerator() { if let id = feature.attributes["OBJECTID"] as? String, !selectedFeatures.contains(id) { selectedFeatures.insert(id) } } } completion?() } }else{ completion?() } }

Based on your reply I made this code above to try and save off the "OBJECTID" from any selected features, but it doesn't appear that the attribute exists in the geodatabase file I'm working with.  Is that a standard/system/ESRI attribute, or am I doing something incorrectly above?

0 Kudos
Ting
by Esri Contributor
Esri Contributor

For service geodatabases, object ID is the primary key for the tables. The point is to find a unique identifier for your feature to compare to. Feel free to pick the identifier that suit your data.

0 Kudos
TravisYordi
New Contributor II

actually yes, it appears we do have OBJECTID, but it's not a string so the cast was failing.  I think I'll be able to maintain my own list of selected object IDs and make it work.  Thank you for the help!