|
POST
|
Hi. The problem you're seeing is that you're trying to use an AGSLayer that is already being used. In Runtime, for performance reasons, many objects cannot be used (or "owned") by more than one other object. In this case, your AGSMap owns a AGSBasemap which owns the layer you are reusing (Coast_url). Your code is trying to create a new AGSBasemap with a layer that is already "owned" by map's current basemap. If you look in the Xcode console, you will see the following when you try to create the new AGSMap. ArcGIS Runtime Error Occurred. Set a breakpoint on C++ exceptions to see the original callstack and context for this error: Error Domain=com.esri.arcgis.runtime.error Code=24 "Object is already owned." UserInfo={NSLocalizedFailureReason=Already owned., NSLocalizedDescription=Object is already owned., Additional Message=Already owned.} Instead, you will need to create an entirely new AGSArcGISMapImageLayer (or AGSArcGISTiledLayer) and use that in a new AGSBasemap. You could modify your code like this to achieve this, using an enum instead of strings… enum CustomBasemap {
enum Language {
case EN
case AR
}
case coast
case satellite
case hybrid(language: Language)
case streetmap(language: Language)
func getLayer() -> AGSLayer {
switch self {
case .coast:
return AGSArcGISMapImageLayer(url: URL(string:"https://services.gisqatar.org.qa/server/rest/services/Vector/Coast/MapServer")!)
case .satellite:
return AGSArcGISTiledLayer(url: URL(string:"https://services.gisqatar.org.qa/server/rest/services/Imagery/QatarSatelitte/MapServer")!)
case .hybrid(let language):
switch language {
case .EN:
return AGSArcGISTiledLayer(url:URL(string: "https://services.gisqatar.org.qa/server/rest/services/Vector/Qatar_StreetMap_Hybrid_E/MapServer")!)
case .AR:
return AGSArcGISTiledLayer(url:URL(string: "https://services.gisqatar.org.qa/server/rest/services/Vector/Qatar_StreetMap_Hybrid_Ar_Test/MapServer")!)
}
case .streetmap(let language):
switch language {
case .EN:
return AGSArcGISTiledLayer(url:URL(string: "https://services.gisqatar.org.qa/server/rest/services/Vector/Qatar_StreetMap_E/MapServer")!)
case .AR:
return AGSArcGISTiledLayer(url:URL(string: "https://services.gisqatar.org.qa/server/rest/services/Vector/Qatar_StreetMap_A/MapServer")!)
}
}
}
func getBasemap() -> AGSBasemap {
return AGSBasemap(baseLayer: getLayer())
}
} and then set the basemap on the existing map (which will save you creating a new map and setting initialViewpoints): var basemapType: CustomBasemap = .coast
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.map = AGSMap(basemap: basemapType.getBasemap())
self.mapView.map = self.map
self.SetMapExtent()
}
...
func Coast_Map_Tapped() {
basemapType = .coast
self.map.basemap = basemapType.getBasemap()
} Hope that helps.
... View more
03-15-2022
07:48 AM
|
0
|
1
|
3047
|
|
POST
|
One other question: Can you open this vector tile layer in ArcGIS Pro or ArcGIS Online and see if it behaves the same way please?
... View more
03-14-2022
01:18 PM
|
0
|
1
|
1452
|
|
POST
|
Hi, There isn't any feature reduction built into Vector Tiles, so this seems like it must be a function of how the tiles are baked. Are you creating the vector tiles in ArcGIS Pro? Is there a rendering rule in Pro that would remove this feature as you zoom out?
... View more
03-10-2022
10:34 AM
|
0
|
2
|
1468
|
|
POST
|
Hi, You can, but bear in mind a couple of things: The Map Service must allow its layers to be queried (you can see the Query action is enabled on this sublayer, for example so that will work in this case), and Map Image Services are rendered on the server. These days we recommend using Feature Services if you can as clients can do so much in terms of rendering now (and in fact querying may be quicker as Runtime might be able to use its in-memory feature cache). You would have to create a new AGSServiceFeatureTable for each sublayer (not for display) and query that, or else simply add those layers to the map instead of the single MapImageService. If you must use MapImage services (but again, in general I'd recommend publishing as Feature Services and using Feature Layers in the app), you can try this modification to the above code… let featureTablesForMapLayers = self.map.operationalLayers.compactMap {
$0 as? AGSArcGISMapImageLayer
}.flatMap { [self] (mapImageLayer: AGSArcGISMapImageLayer) -> [URL] in
let allSublayers = mapImageLayer.mapImageSublayers.flatMap({
self.getAllSublayers(for:$0 as! AGSArcGISMapImageSublayer)
})
let visibleSubLayers = allSublayers.filter { $0.isVisible(atScale: currentScale) }
return visibleSubLayers.compactMap {
if let layer = $0 as? AGSArcGISSublayer {
return mapImageLayer.url!.appendingPathComponent("\(layer.sublayerID)")
}
return nil
}
}.map {
AGSServiceFeatureTable(url: $0)
}
let featureTablesForFeatureLayers = self.map.operationalLayers.compactMap {
// Let's reduce the layers to just AGSFeatureLayers
$0 as? AGSFeatureLayer
}.filter {
// Clear the selection from any previous selection.
$0.clearSelection()
// Make sure we just consider the currently visible layers.
// Just return true if you want all layers, regardless of current scale.
return $0.isVisible(atScale: currentScale)
}.compactMap {
// Convert the sequence to AGSFeatureTables
$0.featureTable
}
let allFeatureTablesInMap = featureTablesForMapLayers + featureTablesForFeatureLayers
allFeatureTablesInMap.forEach { featureTable in
// Query each feature table, coordinating responses using the DispatchGroup.
queryGroup.enter()
... And add this method… func getAllSublayers(for layer: AGSLayerContent) -> [AGSLayerContent] {
if layer.subLayerContents.isEmpty {
return [layer]
} else {
let allSublayers = layer.subLayerContents
.compactMap({ $0 as? AGSArcGISSublayer })
.flatMap {
getAllSublayers(for: $0)
}
return allSublayers
}
} Note that selection is not possible when using MapImageServices.
... View more
03-10-2022
10:23 AM
|
0
|
0
|
3944
|
|
POST
|
Hi @syamkumarsurendran, If I understand your question correctly, you want the current location (aka the blue dot) to be closer to the bottom of the screen than the default center. The property to control this is on LocationDisplay (which you can get from MapView.getLocationDisplay()). See setNavigationPointHeightFactor(), and you might also find setWanderExtentFactor() useful too. Hope that helps!
... View more
03-09-2022
08:56 AM
|
0
|
1
|
1171
|
|
POST
|
Hi @TayLee, This is possible, yes. Check out this gist for an example and make sure that you've got your world file and auxiliary projection file set up properly (you will need both). Please note that accessing a local raster file like this will require a Standard Runtime license when you deploy your app. Hope this helps.
... View more
03-09-2022
08:38 AM
|
0
|
0
|
842
|
|
POST
|
Do you get the same error if you skip the elevation source? https://alkhazna.gisqatar.org.qa/server/rest/services/CGISRASTER/QATAR_2021_09_C050_PLE/ImageServer is not accessible to me to test against.
... View more
03-07-2022
11:12 AM
|
0
|
0
|
1921
|
|
POST
|
I see. So what you need to do is perform a Query (or multiple queries, if there are many layers). Identify is designed to handle click or tap events on the map to find out what the user tapped on. Query is much more powerful, and executes against the tables behind each layer in the map. What you might do it look at the AGSMap.operationalLayers and determine a set of feature layers that you want to get information about. For each of those feature layers, get the feature table, create an AGSQueryParameters, set the geometry and spatialRelationship on that AGSQueryParameters to the geometry you get back from the sketch editor, and call query on the feature table. Attached is a project that does this. Here's what the code looks like: func queryMapLayers(for polygon: AGSPolygon) {
let queryParameters = AGSQueryParameters()
queryParameters.spatialRelationship = .intersects
queryParameters.geometry = polygon
map.load { [weak self] error in
guard let self = self else { return }
if let error = error {
print("Error loading the map: \(error.localizedDescription)")
return
}
// Synchronize our results
let queryGroup = DispatchGroup()
var queryResults = [AGSFeatureTable: AGSFeatureQueryResult]()
var queryErrors = [AGSFeatureTable: Error]()
let currentScale = self.mapView.mapScale
self.map.operationalLayers.lazy.compactMap {
// Let's reduce the layers to just AGSFeatureLayers
$0 as? AGSFeatureLayer
}.filter {
// Clear the selection from any previous selection.
$0.clearSelection()
// Make sure we just consider the currently visible layers.
// Just return true if you want all layers, regardless of current scale.
return $0.isVisible(atScale: currentScale)
}.compactMap {
// Convert the sequence to AGSFeatureTables
$0.featureTable
}.forEach { featureTable in
// Query each feature table, coordinating responses using the DispatchGroup.
queryGroup.enter()
print("Querying \(featureTable.tableName)")
featureTable.queryFeatures(with: queryParameters) { [featureTable] queryResult, error in
print("Queryied \(featureTable.tableName)")
defer {
// Let the dispatch group know we're done waiting for this table's query.
queryGroup.leave()
}
if let error = error {
// Save the error to handle as a batch once everything returns using DispatchGroup.
queryErrors[featureTable] = error
return
}
// Save the results to look at later, once ALL tables have responded.
queryResults[featureTable] = queryResult
// Or look at the results as we get them
if let queryResult = queryResult {
// Output the results as we get them
print("Got results for table \(featureTable.tableName)")
let layer = featureTable.layer as? AGSFeatureLayer
layer?.clearSelection()
for case let feature as AGSFeature in queryResult.featureEnumerator() {
print(feature)
layer?.select(feature)
}
}
}
}
// You could handle errors and results as they come in above, but with a DispatchGroup,
// you can control whether to wait for all results/errors to come back before doing
// something. In this case we're handling each result as it comes in above, but coordinating
// all errors we might get back.
queryGroup.notify(queue: .main) {
if !queryErrors.isEmpty {
print("\(queryErrors.count) tables failed on query:")
queryErrors.forEach { table, error in
print("Error querying table \(table.tableName): \(error)")
let layer = table.layer as? AGSFeatureLayer
layer?.clearSelection()
}
}
// We looked at each result as it came in above, but we could instead/also look at them
// all together here.
// queryResults.forEach { table, result in
// print("Got results for table \(table.tableName)")
//
// let layer = table.layer as? AGSFeatureLayer
// layer?.clearSelection()
//
// for case let feature as AGSFeature in result.featureEnumerator() {
// print(feature)
// layer?.select(feature)
// }
// }
}
}
} Hope this helps!
... View more
03-07-2022
11:09 AM
|
1
|
3
|
3968
|
|
POST
|
Hi, A graphic or feature displayed in a map combines a geometry (the shape) and a dictionary of attributes. The AGSSketchEditor purely works with geometries and doesn't relate to the feature or graphic itself. If you need to edit a feature or a graphic, you select or identify that feature or graphic, get its geometry, and pass that to the sketch editor. When the sketch editor is done, you updated the geometry on the original feature or graphic with the modified one the sketch editor provides, but it's up to you to keep hold of the feature or graphic. If you need to get hold of a feature or graphic to obtain the attribution, call AGSMapView.identify() (there are a few different overloads of that method you can use). You can then get the AGSGraphic or AGSFeature the user tapped on and use the attributes dictionary to get or set attribute values. Take a look at these samples for some more info: https://developers.arcgis.com/ios/swift/sample-code/identify-graphics/ https://developers.arcgis.com/ios/swift/sample-code/identify-layers/ And a sample showing reading attributes Hope that helps.
... View more
03-02-2022
07:15 AM
|
0
|
5
|
4006
|
|
POST
|
Hi @VinceCarloSantos, I'm betting that the AGSSurface() is deallocated before the completion block is called. Just set up an instance level variable to hold on to the surface. Incidentally, you don't need to explicitly call load(). When you call any asynchronous method on a loadable object, we'll automatically load it if it isn't already loaded. That can help to make your code a little cleaner. You can start to avoid these sorts of issues by adopting Swift Concurrency. You can read more about that in this blog post. Hope that helps! Nick.
... View more
03-02-2022
06:44 AM
|
0
|
0
|
711
|
|
POST
|
Hi @GWaltersOZ. Licensing is per app per user per device. Please see this documentation for more info. If you have your app installed on 100 iPads, but only 50 iPads are ever in use at one time, you will need 100 licenses. You could uninstall an app from one device (undeploy 1 license) and reinstall it on another (redeploy that license), but it's up to you to ensure you don't exceed your total number of purchased deployments. We don't build in a way to audit deployments because license strings are designed to be used in completely disconnected scenarios, but since you will be connected, you could build your own solution that phones home so that you know you're staying within your deployment limit. I also want to make sure that you know you can access secured services with a Lite license as long as you are not editing. I expect you know that, but since you don't explicitly mention editing above I felt it best to mention it here! Hope this helps.
... View more
02-16-2022
08:58 PM
|
0
|
1
|
1744
|
|
POST
|
When you download an offline map using the OfflineMapTask, Runtime will create a folder at the location you specify when you create the new GenerateOfflineMapJob, and will download and write a number of resources into that folder. As you noticed, there is a .mmap file, but you should consider the folder to be a black box. To re-use the already downloaded offline map, as @JoeHershman shows below, you create a new MobileMapPackage with the same folder you specified when you downloaded the offline map. Then get the first map from it. See this documentation for more info. Note, you do not need to explicitly load the map or its operational layers unless you plan on reading their metadata before they are displayed in a map view.
... View more
02-15-2022
01:28 PM
|
1
|
0
|
1580
|
|
POST
|
HI @FatmaAkdemir, thanks for the question. You would not encounter an error, but you would be in violation of your license terms. When using a Basic, Standard, or Advanced license string, it's up to you as the developer to ensure that the license string is used within the limits of the license pack you purchased (in this case, no more than 25). You can find out more information here. Hope that helps!
... View more
02-11-2022
02:27 PM
|
1
|
0
|
1860
|
|
POST
|
Hi David. If you have the legend info, you should have access to the symbol. How you get the color out of that symbol will depend on the type of symbol and how the web map was authored, but explore the various symbol types (SimpleLineSymbol, CompositeSymbol, MultilayerPolylineSymbol) which should eventually lead you to a color. Hope that helps.
... View more
01-31-2022
07:54 AM
|
1
|
5
|
2375
|
|
POST
|
Hi @NelsonC, You can call getCustomParameters() on your WmtsLayer, which gives you a modifiable Map of Strings that will allow you to set the "key" custom parameter. In other words, leave the URL without the ?key part and add a "key" to the Map. Hope that helps!
... View more
01-06-2022
07:29 AM
|
1
|
0
|
1087
|
| Title | Kudos | Posted |
|---|---|---|
| 2 | 05-14-2026 07:07 AM | |
| 2 | 04-30-2026 10:59 AM | |
| 4 | 04-22-2026 08:07 AM | |
| 1 | 01-29-2026 09:39 AM | |
| 1 | 12-17-2025 10:12 AM |