I have a feature layer that I can load up, view, and tap without issues. I'm able to see the built-in popup that ArcGIS provides that allows me to edit particular parts of each feature. Here is the code that I'm using.
let item = AGSPortalItem(portal: self.portal, itemID:"PORTALITEMID")
self.featureTable = AGSServiceFeatureTable(item: item, layerID: 0)
self.waypointFeatureLayer = AGSFeatureLayer(featureTable: self.featureTable)
self.mapView.map?.operationalLayers.add(waypointFeatureLayer!)
However, when I attempt to query the layers, I run into issues. The query works (I only see what I'm looking for), but I can't tap on any features. I get the following error.
Error Domain=com.esri.arcgis.runtime.error Code=2 "Invalid argument." UserInfo={NSLocalizedFailureReason=Unable to run identify on the specified layer.
Layer not identifiable
layer not found in view., NSLocalizedDescription=Invalid argument., Additional Message=Unable to run identify on the specified layer.
Layer not identifiable
layer not found in view.}
The code I'm using to identify the queried layer is as follows.
guard self.waypointFeatureCollectionLayer != nil else { return }
guard let featureLayer = self.waypointFeatureCollectionLayer.layers.first else {
fatalError()
}
mapView.identifyLayer(featureLayer, screenPoint: screenPoint, tolerance: 12, returnPopupsOnly: false) { [weak self] (result: AGSIdentifyLayerResult) in
guard let self = self else { return }
if let error = result.error {
log.error(error)
} else if !result.popups.isEmpty {
// Unselect the previous feature.
featureLayer.clearSelection()
// Select the new feature.
let features = result.geoElements as? [AGSFeature]
let selectedFeature = features?.first
featureLayer.select(selectedFeature!)
// Display a popup only if it exists.
let popupsViewController = AGSPopupsViewController(popups: result.popups)
// Display the popup as a formsheet -- specified for iPads.
popupsViewController.modalPresentationStyle = .formSheet
// Present the popup.
popupsViewController.delegate = self
self.present(popupsViewController, animated: true)
}
}
And the code to load the queried layer is...
let item = AGSPortalItem(portal: self.portal, itemID: "PORTALITEMID")
self.featureTable = AGSServiceFeatureTable(item: item, layerID: 0)
let queryParams = AGSQueryParameters()
if let primaryUser = CoreDataManager().getPrimaryUser() {
let clause = "CustomerID=\(primaryUser.id)"
queryParams.whereClause = clause
self.featureTable.queryFeatures(with: queryParams) { [weak self] (queryResult: AGSFeatureQueryResult?, error: Error?) in
if let error = error {
log.error("Error querying feature table: \(error)")
} else {
guard let queryResult = queryResult else { return }
let featureCollectionTable = AGSFeatureCollectionTable(featureSet: queryResult)
let featureCollection = AGSFeatureCollection(featureCollectionTables: [featureCollectionTable])
self?.waypointFeatureCollectionLayer = AGSFeatureCollectionLayer(featureCollection: featureCollection)
self?.mapView.map?.operationalLayers.add(self?.waypointFeatureCollectionLayer)
}
}
} else {
assertionFailure()
}
The one difference I notice is that the queried layer is actually a collection layer. When I run the identifyLayer above, I can find a reference to one layer by using collectionLayer.layers.first (note that this does return a result, so my assumption is it's the layer with my queried features)
Is there a way to make queried layers identifiable?
Side note...when I query layers they also show only small black dots, unlike the different types of dots (red/black) on the unqueried layers. I'm not sure if this is something by design or if there is a way to edit how they look after they are queried. The issue with tapping/editing the layers is more important however.
Notes:
Using arcgis-runtime-ios 100.11.2
Followed these docs
https://developers.arcgis.com/ios/swift/sample-code/feature-collection-layer-query/
https://developers.arcgis.com/ios/swift/sample-code/add-features-feature-service/
https://developers.arcgis.com/ios/swift/sample-code/show-popup/
Solved! Go to Solution.
Hi,
I think you're doing too much here. It'll help to know what you're trying to achieve. But in short, this code is great:
let item = AGSPortalItem(portal: self.portal, itemID:"PORTALITEMID")
self.featureTable = AGSServiceFeatureTable(item: item, layerID: 0)
self.waypointFeatureLayer = AGSFeatureLayer(featureTable: self.featureTable)
self.mapView.map?.operationalLayers.add(waypointFeatureLayer!)
You've just added an AGSFeatureLayer to your map's operational layers which references Layer 0 in the PORTALITEMID service.
So far so good.
The identify code is also good, but you should identify on self.waypointFeatureLayer instead of self.waypoinyFeatureCollectionLayer.layers.first. I'll explain why I think that…
Where you're tripping up is that you're creating a new service feature table (even though it's pointing at the same service) and calling query, then creating a NEW layer (in this case an AGSFeatureCollectionLayer) and adding that to the map. That is now a layer that is not connected to the source service, but exists in-memory only. Furthermore, it only contains a subset of the data in the source service at PORTALITEMID (features which match CustomerID=primaryUser.id).
So, you should still see the original data in self.waypointFeatureLayer, AND the copy of some of that data in self.waypointFeatureCollectionLayer which you added when you did the query (see below for why that's a black dot). And yes, it's actually in a sublayer of that AGSFeatureCollectionLayer, but that's not important for right now.
Where I'm lost is how your Identify and Query work together in your use case. What I suspect is that you don't need that additional feature collection layer at all. Simply have self.featureTable and self.waypointFeatureLayer. Call Identify on self.waypointFeatureLayer in response to a user tap event.
If you need to query, just query using the existing self.featureTable. But what you do with the results might be suspect. I wonder if you might just want to set the definitionExpression on self.waypointsFeatureLayer to "CustomerID=\(primaryUser.id)". That will limit what's being displayed. Or if you want to highlight an identified feature, perhaps you mean to select it in self.waypointsFeatureLayer (there's actually a selectWithQuery method that could save you a lot of code if that's really what you want to do).
I hope that helps, but I'm flying a bit blind. If you need some more help, I'm happy to dig in a bit but can you explain what your goal is here? I'm pretty certain we can achieve it in a much simpler way.
BTW, the data in self.waypointFeatureCollectionLayer is a black dot because there is no renderer defined on the layer. And that's expected, because you created a vanilla AGSFeatureCollectionLayer and populated it with raw data from the query. On the other hand, self.waypointFeatureLayer is able to look at the source service's Layer 0 and read a renderer definition from the JSON metadata it finds there.
Hi,
I think you're doing too much here. It'll help to know what you're trying to achieve. But in short, this code is great:
let item = AGSPortalItem(portal: self.portal, itemID:"PORTALITEMID")
self.featureTable = AGSServiceFeatureTable(item: item, layerID: 0)
self.waypointFeatureLayer = AGSFeatureLayer(featureTable: self.featureTable)
self.mapView.map?.operationalLayers.add(waypointFeatureLayer!)
You've just added an AGSFeatureLayer to your map's operational layers which references Layer 0 in the PORTALITEMID service.
So far so good.
The identify code is also good, but you should identify on self.waypointFeatureLayer instead of self.waypoinyFeatureCollectionLayer.layers.first. I'll explain why I think that…
Where you're tripping up is that you're creating a new service feature table (even though it's pointing at the same service) and calling query, then creating a NEW layer (in this case an AGSFeatureCollectionLayer) and adding that to the map. That is now a layer that is not connected to the source service, but exists in-memory only. Furthermore, it only contains a subset of the data in the source service at PORTALITEMID (features which match CustomerID=primaryUser.id).
So, you should still see the original data in self.waypointFeatureLayer, AND the copy of some of that data in self.waypointFeatureCollectionLayer which you added when you did the query (see below for why that's a black dot). And yes, it's actually in a sublayer of that AGSFeatureCollectionLayer, but that's not important for right now.
Where I'm lost is how your Identify and Query work together in your use case. What I suspect is that you don't need that additional feature collection layer at all. Simply have self.featureTable and self.waypointFeatureLayer. Call Identify on self.waypointFeatureLayer in response to a user tap event.
If you need to query, just query using the existing self.featureTable. But what you do with the results might be suspect. I wonder if you might just want to set the definitionExpression on self.waypointsFeatureLayer to "CustomerID=\(primaryUser.id)". That will limit what's being displayed. Or if you want to highlight an identified feature, perhaps you mean to select it in self.waypointsFeatureLayer (there's actually a selectWithQuery method that could save you a lot of code if that's really what you want to do).
I hope that helps, but I'm flying a bit blind. If you need some more help, I'm happy to dig in a bit but can you explain what your goal is here? I'm pretty certain we can achieve it in a much simpler way.
BTW, the data in self.waypointFeatureCollectionLayer is a black dot because there is no renderer defined on the layer. And that's expected, because you created a vanilla AGSFeatureCollectionLayer and populated it with raw data from the query. On the other hand, self.waypointFeatureLayer is able to look at the source service's Layer 0 and read a renderer definition from the JSON metadata it finds there.
@Nicholas-Furness thanks so much for your detailed explanation.
I apologize for the confusion, I realize I probably didn't explain myself as I wanted. In fact, my Identify would NOT work with my query, which was the reason for this post. It only works without using the query.
In short, I have multiple users submitting features to the same feature layer, but I only want they features that they submitted themselves to be visible, hence the query.
Your suggestion of using definitionExpression worked exactly how I wanted though! So I really appreciate the help on that. I'm now able to view only the features that are submitted by the user as well as tap an edit eat feature. Thanks so much again for your prompt and accurate response!