|
POST
|
Hi Karsten, This is the repo I was talking about, but this was written for the previous version of the Runtime SDKs (version 10.x). It will not work with the 100.x versions. Stream Layer support is on the roadmap, but it will be a few releases before it will be ready. If you want to adapt the 10.x stream layer support from the above repo, you could borrow the logic for reading data from that, and look at how the runtime-100 branch of this repo uses AGSFeatureCollectionLayer to present data from a custom source (an AGSFeatureCollectionLayer is an in-memory feature layer - you just need to load data into it). Hope that helps. Nick.
... View more
12-23-2021
08:57 AM
|
1
|
0
|
1053
|
|
POST
|
Hi @Selsoe, I'm not too familiar with WMTS, but if you can DM me a link to the Xcode project, I'll see if I can find someone who can help be dig into this a bit more. Have you been able to open both of these in the ArcGIS Online Map Viewer?
... View more
12-16-2021
08:40 AM
|
0
|
1
|
6956
|
|
BLOG
|
We've just rolled out version 100.13 of the ArcGIS Runtime SDKs. You can read all about the great new functionality in the overall release blog post, but there's one change that will specifically impact iOS developers… If you manually integrated the SDK into your project, we now provide two xcframework files: ArcGIS.xcframework, and Runtimecore.xcframework. Please be aware that when you update to 100.13, you will need make sure you also include Runtimecore.xcframework in the Frameworks, Libraries and Embedded Content section of your project's target's build settings. Of course, this doesn't affect developers using Cocoapods or Swift Package Manager, which will automatically integrate the change when you update (in case you needed another excuse to adopt Swift Package Manager). We've also made a couple of minor fixes to the Toolkit. Beyond that, make sure to read the release notes, and try out some of the new functionality. My personal highlights are vector tiles in 3D, the updates to using I3S versions 1.7 and 1.8 with 3D services (3D apps will use less memory, feel snappier, and put less drain on the battery when working with updated services), and improved control over labeling conflicts.
... View more
12-15-2021
11:07 AM
|
1
|
0
|
2637
|
|
POST
|
Hi. See the License and Deployment section. In particular the License your app with a license string section will help you, including providing your Lite license string. You can also read about the requirements for deploying an app here. Hope that helps.
... View more
12-13-2021
08:43 AM
|
2
|
1
|
1208
|
|
POST
|
Hi @Selsoe, At the moment we don't really have a generic solution. In your case, you could potentially look at the AGSLocationDisplay's autoPanMode to get an idea. If the user pinches while auto-panning, then autoPan mode is turned off (the exception is double-tap-to-zoom). In the meantime, I'll see if we can expose some information about that in a future release.
... View more
12-08-2021
08:54 AM
|
1
|
0
|
896
|
|
POST
|
@Anonymous User: I've corrected my response above. It was nearly there, but the trick is to set maxScale. The layers should upscale by default. Apologies for the confusion. Let us know how that works for you.
... View more
12-03-2021
07:28 AM
|
0
|
0
|
2625
|
|
POST
|
Hi @Anonymous User. You should dig into the AGSMap's basemap and set the noDataTileBehavior maxScale on any image tile layers it contains. Try something like this: func removeTiledBasemapMaxScale(for map: AGSMap) {
map.load { error in
if let error = error {
// Do whatever error handling you might do in your app here.
print("Error loading map: \(error)")
return
}
map.basemap.baseLayers.forEach {
($0 as? AGSLayer)?.maxScale = 0
}
// Probably also good to set the reference layers.
map.basemap.referenceLayers.forEach {
($0 as? AGSLayer)?.maxScale = 0
}
}
} You'll want to do this each time you open the map - this won't get persisted to the downloaded map.
... View more
12-02-2021
03:21 PM
|
0
|
0
|
2673
|
|
POST
|
Hi @frankm, Preplanned offline maps are generated from the web map definition, so you would need to include the tables in the web map itself. Here's an example web map that includes a layer and a table. You can add tables to your web map by using the Add Data option in the ArcGIS Online map viewer. For example, here's a link to a table that can be added that way: https://services.arcgis.com/OfH668nDRN7tbJh0/arcgis/rest/services/Service_with_table_test/FeatureServer/1 Once the web map contains a table (and if that table belongs to a service that is offline enabled), it'll be packaged up with the rest of the preplanned map content. However, when I say "table" above, I mean an actual table definition on the service, which is exposed in Runtime via AGSMap.tables. See this example service definition which lists both a layer and a table: From your description, it might be the case that rather than using an explicit table, you're using a layer and just not setting a geometry on the records you put in there. If you added that to the web map, as you say, that would get added as another layer, and not as a table, and you'd access it via AGSMap.operationalLayers. If that's the case, you have two options: Republish those "geometry-less" layers as tables and include them in the web map. Download and sync those layers in parallel with your offline map using a separate AGSGeodatabaseSyncTask. Option 2 would involve some custom logic in your app to download and sync the geodatabase alongside the Offline Map. I.e. you would have an AGSOfflineMapTask and an AGSGeodatabaseSyncTask (or, more accurately, an AGSDownloadPreplannedOfflineMapJob and AGSGenedateGeodatabaseJob) working alongside one-another. Once downloaded, you can access the tables from the AGSGeodatabase and use them as before. When you sync the offline map with the AGSOfflineMapSyncTask, you'd also sync the downloaded geodatabase. Note that if you download the AGSGeodatabase and add the feature tables to the offline AGSMap's tables collection, then the AGSOfflineMapSyncTask should include those tables in the sync. I say "should" because I haven't tested that to confirm. That feels like a bit of a fragile solution given the alternative, and you'd have to accommodate for a few potential error cases. There are some other nuances to this too. Happy to discuss if this is the way you decide to go. But if at all possible, I'd recommend you go for option 1. Note, if you were downloading on-demand offline maps rather than preplanned offline maps, then you could potentially use overrides to include the additional tables that aren't part of the source web map, but then you're foregoing the benefits of preplanned offline maps. Hope that helps.
... View more
11-30-2021
08:23 AM
|
1
|
1
|
1301
|
|
BLOG
|
Apple recently introduced long-awaited Swift Concurrency capabilities, also known as “async/await”. In this blog post we’ll discuss how to use async/await with the ArcGIS Runtime SDK for iOS and introduce a new Swift package to help. What is Swift Concurrency? In brief, Swift Concurrency lets you ditch callback hell and write your asynchronous code in an easy to understand linear sequence. Using it, you can write code like this: func openMapFromMobileMapPackage() async throws {
let mmpk = AGSMobileMapPackage(fileURL: mmpkURL)
try await mmpk.load()
guard let map = mmpk.maps.first else {
preconditionFailure("MMPK contains no maps!")
}
mapView.map = map
try await map.load()
if let sr = map.spatialReference {
showAlert(title: "Map Details", message: "The map's WKID is \(sr.wkid)")
}
} Whereas without it you'd have to write this: func openMapFromMobileMapPackage() {
let mmpk = AGSMobileMapPackage(fileURL: mmpkURL)
mmpk.load { [weak self] error in
guard let self = self else { return }
if let error = error {
self.showError(error)
return
}
guard let map = mmpk.maps.first else {
preconditionFailure("MMPK contains no maps!")
}
self.mapView.map = map
map.load { [weak self] error in
guard let self = self else { return }
if let error = error {
self.showError(error)
return
}
if let sr = map.spatialReference {
self.showAlert(title: "Map Details",
message: "The map's WKID is \(sr.wkid)")
}
}
}
} Later in this post we’ll dig further into the benefits that Swift Concurrency delivers, but first let’s look at how Apple brings it to your existing projects, and what that means for ArcGIS Runtime. Swift Concurrency support Firstly, let's set some expectations: Swift Concurrency requires Xcode 13 and targets iOS 15 (although after some spirited feedback from the Apple developer community, Xcode 13.2 will expand support to include iOS 13 and 14 - and yes, it is also supported on macOS and watchOS, but in this post we're focusing on the ArcGIS Runtime SDK for iOS). To use a method with Swift Concurrency, it must be defined with the new async keyword. Fortunately, if you’re working with an API that doesn’t explicitly do this (that includes all Objective-C APIs and any Swift API that hasn't been updated very recently), then Apple have provided an elegant solution: Xcode will automatically generate a virtual async wrapper method around any method that takes a completion block as its last parameter. That’s what happened above with the load() methods on AGSMap and AGSMobileMapPackage; we didn’t have to do anything to access an async version of load(completion:). Needless to say, this makes adopting Swift Concurrency with existing asynchronous APIs really easy. Kudos to Apple for doing this. However there’s one scenario where Xcode can’t create these virtual wrappers, and unfortunately it affects most asynchronous methods in the ArcGIS Runtime SDK for iOS… Return values and completion blocks The problem is that if, unlike the load() method, the original completion-block based method also returns a value, then Xcode can’t be sure how to generate the new virtual wrapper. Let's explain what I mean by that with a concrete example. Here’s how the geocode(searchText:) method is defined on AGSLocatorTask: -(id<AGSCancelable>)geocodeWithSearchText:(NSString *)searchText
completion:(void(^)(NSArray<AGSGeocodeResult*> * __nullable geocodeResults, NSError * __nullable error))completion; This is automatically bridged to Swift as: func geocode(withSearchText searchText: String,
completion: @escaping ([AGSGeocodeResult]?, Error?) -> Void) -> AGSCancelable When the geocode completes, this method passes an [AGSGeocodeResult] array (or an Error) to the completion block. The problem is that it also returns an AGSCancelable to the caller when the geocode is initiated. So, if we create an async wrapper, what should it return? Should it return the [AGSGeocodeResult] array, or the AGSCancelable? To us as developers, it’s probably clear which of these is of real interest. We want something like this that returns the [AGSGeocodeResult] array (or throws an Error) : func geocode(withSearchText searchText: String) async throws -> [AGSGeocodeResult] But it's not so clear to Xcode, and it can’t make that decision automatically. On the other hand, we can implement a wrapper like this ourselves and be explicit about what we choose to return (and the method above still supports cancelation, which we'll discuss below). It turns out that a lot of asynchronous methods in the Runtime SDK fall into this category (over 170 of them!). Here are just a few examples: Querying for features or related records. Solving a route. Geocoding. Posting edits to a feature service. Adding, updating, deleting, or fetching attachments. Interrogating local edits waiting to be written/posted to storage. Downloading/synchronizing offline maps and offline data. Identifying in layers or graphics overlays. Reading and tracing utility networks. Calculating service areas. Animating a map view or scene view. Following a route with the navigation API. Working with geodatabase versions. Creating symbol swatches. Working with Portal Items. Searching a portal. Even if you only use a handful of these 170+ methods, creating async wrappers is a repetitive task and can be a lot to get your head around if you’re new to it. So we did it for you… Introducing the ArcGIS Runtime async/await package We’re pleased to present a handy set of open source Swift files that fill the gap, implementing the async wrappers that Xcode could not: arcgis-runtime-ios-async-await Now you can turn this… func getRoute(origin: AGSStop, destination: AGSStop, completion: @escaping (AGSRouteResult?, Error?) -> Void) {
let startTime = Date()
// Get default route parameters (first time, this will load the AGSRouteTask)
routeTask.defaultRouteParameters { [weak self] routeParameters, error in
guard let self = self else {
completion(nil, nil) // Don't forget to call the completion! Or is this a fatalError?
return
}
if let error = error {
print("Error getting default route parameters: \(error.localizedDescription)")
completion(nil, error) // Don't forget to call the completion!
return
}
guard let routeParameters = routeParameters else {
fatalError("We got neither route parameters NOR an error!")
}
routeParameters.setStops([origin, destination])
self.routeTask.solveRoute(with: routeParameters) { routeResult, error in
let calculationTime = Date().timeIntervalSince(startTime)
print("Route calculation took about \(String(format: "%.1f", calculationTime)) seconds")
completion(routeResult, error) // Don't forget to call the completion!
}
}
} Into this… func getRoute(origin: AGSStop, destination: AGSStop) async throws -> AGSRouteResult {
let startTime = Date()
// Get default route parameters (first time, this will load the AGSRouteTask)
let routeParameters = try await routeTask.defaultRouteParameters()
// Modify the parameters with the stops we're interested in.
routeParameters.setStops([origin, destination])
// Calculate a route between the stops.
let result = try await routeTask.solveRoute(with: routeParameters)
let calculationTime = Date().timeIntervalSince(startTime)
print("Route calculation took about \(String(format: "%.1f", calculationTime)) seconds")
return result
} While still supporting cancelation. That’s pretty neat, right? As you might have noticed, Swift Concurrency is about more than just flattening callback hell. There are actually a few potential bugs that have been cleaned up in the above code: Capturing a weak self reference (and checking it) goes away. That eradicates a host of potential memory leak opportunities. The result is no longer an optional. Either an error is thrown, or a non-optional result is returned. Because errors now throw you don’t have to check for them before you continue. You don’t have to remember to call the completion block through every code path (see how often completion is called in the original code above, and you have to remember to return right after it). It was often far too easy to forget to call back if something untoward happened, leaving the caller waiting forever. Bugs like that are often hard to track down. You don’t have to reference class level properties with self. Your code that handles the result is now scoped at the same level as the code that requested the result. Another helpful side-effect is that you have greater clarity over the lifetime of objects that perform asynchronous work (for example, ArcGIS Tasks like AGSLocatorTask or AGSOfflineMapTask, the various types of AGSJob, or simply querying an AGSFeatureTable). A common trap we see many Runtime developers fall into is that the object performing an asynchronous operation is released before that operation completes, which means that the completion block is never called! For example, this code will silently fail to do anything: func doGeocode(for searchText: String) {
let worldGeocoder = AGSLocatorTask(url: URL(string: "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer")!)
worldGeocoder.geocode(withSearchText: searchText) { [weak self] results, error in
guard let self = self else { return }
if let error = error {
print("Error while geocoding '\(searchText)': \(error.localizedDescription)")
return
}
guard let firstResult = results?.first,
let location = firstResult.displayLocation else { return }
self.geocodeOverlay.graphics.removeAllObjects()
let graphic = AGSGraphic(geometry: location, symbol: AGSSimpleMarkerSymbol(style: .circle, color: .orange, size: 8))
self.geocodeOverlay.graphics.add(graphic)
if let envelope = firstResult.extent {
self.mapView.setViewpointGeometry(envelope, completion: nil)
} else {
self.mapView.setViewpointCenter(location, completion: nil)
}
}
} Can you see why? If you’ve hit this before, you’ll probably see it immediately, but until you develop the muscle memory to look for these cases, they can be hard to spot and diagnose. The worldGeocoder instance created at the very start of the doGeocode() method will go out of scope and be deallocated before it has a chance to complete the geocode() request and call back into the completion handler. Instead, to ensure that worldGeocoder is not deallocated before it delivers the geocode result, you typically store it as an object variable like this: let worldGeocoder = AGSLocatorTask(url: URL(string: "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer")!)
func doGeocode(for searchText: String) {
worldGeocoder.geocode(withSearchText: searchText) { [weak self] results, error in
guard let self = self else { return } But now, with async/await, you can write something like this: func doGeocode(for searchText: String) async throws {
let worldGeocoder = AGSLocatorTask(url: URL(string: "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer")!)
guard let firstResult = (try await worldGeocoder.geocode(withSearchText: searchText)).first,
let location = firstResult.displayLocation else { return }
geocodeOverlay.graphics.removeAllObjects()
let graphic = AGSGraphic(geometry: location, symbol: AGSSimpleMarkerSymbol(style: .circle, color: .orange, size: 8))
geocodeOverlay.graphics.add(graphic)
if let envelope = firstResult.extent {
await mapView.setViewpointGeometry(envelope)
} else {
await mapView.setViewpointCenter(location)
}
} Just as before, worldGeocoder is released when execution leaves doGeocode(), but now, thanks to Swift Concurrency, execution doesn’t leave doGeocode() until after the results have been calculated and the mapView has navigated to show them. Note: Although this highlights a potential benefit of sequential asynchronous execution, you might not actually want to do this. AGSLocatorTask needs to load its metadata before it can be used (which is done automatically when first needed), so storing a single instance to reuse over and over again and loading the metadata only once is probably preferable to creating a new instance and loading metadata for each geocode. Loading metadata is a lightweight operation, but still takes time. How does this magic work? There are great WWDC sessions from Apple watch that cover the new capabilities (start here, and be sure to check out the related videos listed on that page), but let’s provide a quick overview by looking at this code: func getRoute(origin: AGSStop, destination: AGSStop) async throws -> AGSRouteResult {
let startTime = Date()
// Get default route parameters (first time, this will load the AGSRouteTask)
let routeParameters = try await routeTask.defaultRouteParameters() The asynchronous call to routeTask.defaultRouteParameters() is marked with the new await keyword. When await routeTask.defaultRouteParameters() is called, Swift Concurrency will actually pause execution of getRoute() and make the thread it was executing on available for use. It will then execute the guts of defaultRouteParameters() on another thread, and when that finishes executing Swift Concurrency will provide the defaultRouteParameters() return value back on the original thread and we store it in the routeParameters variable. There’s more to it than that, of course, but this is accurate enough for the purposes of understanding and using async/await for now. Swift Concurrency includes many features to help make asynchronous code safer and easier to write, including the await and async keywords, Actors (which help with race conditions), Tasks (which include the cancelation capabilities we hook into with our wrappers), and Task Groups. Again, I encourage you to take a look at the various excellent WWDC sessions on the topic. Canceling and await Our library of async wrappers maintains cancelation capabilities (something Xcode could not automatically do). So how do we cancel something? Let’s take this example again: func getRoute(origin: AGSStop, destination: AGSStop) async throws -> AGSRouteResult {
let startTime = Date()
// Get default route parameters (first time, this will load the AGSRouteTask)
let routeParameters = try await routeTask.defaultRouteParameters()
// Modify the parameters with the stops we're interested in.
routeParameters.setStops([origin, destination])
// Calculate a route between the stop stops.
let result = try await routeTask.solveRoute(with: routeParameters)
let calculationTime = Date().timeIntervalSince(startTime)
print("Route calculation took about \(String(format: "%.1f", calculationTime)) seconds")
return result
} What happens if the solveRoute() method call takes a long time to execute and the user decides they want to cancel the request? If, as I described above, the getRoute() method execution is paused while the defaultRouteParameters() or solveRoute() methods are executing, how can I cancel either of those method calls? The answer is to wrap them in a Task. A Task is a fundamental type introduced by Swift Concurrency, and it includes a framework for cancelation. You end up with code like this: @MainActor
var cancelableTask: Task<AGSRouteResult, Error>? = nil
func getRoute(origin: AGSStop, destination: AGSStop) async throws -> AGSRouteResult {
cancelableTask = Task { () -> AGSRouteResult in
let startTime = Date()
// Get default route parameters (first time, this will load the AGSRouteTask)
let routeParameters = try await routeTask.defaultRouteParameters()
// Modify the parameters with the stops we're interested in.
routeParameters.setStops([origin, destination])
// Calculate a route between the stop stops.
let result = try await routeTask.solveRoute(with: routeParameters)
let calculationTime = Date().timeIntervalSince(startTime)
print("Route calculation took about \(String(format: "%.1f", calculationTime)) seconds")
return result
}
return try await cancelableTask!.value
}
@IBAction func userTappedCancel(sender: UIButton) {
cancelableTask?.cancel()
} Tasks are used to asynchronously execute a block of code. In the above code, we hold on to the Task in the cancelableTask variable (the @MainActor modifier makes sure that we don’t enter race conditions while manipulating the cancelableTask variable). If the user wants to cancel, we just call the Task’s cancel() method. Our custom wrappers integrate with this and take care of canceling the Runtime calls. As a result of canceling the Runtime call, getRoute() will throw a userCancelled error, which you can catch and report with something like this: do {
// Cancelations are propagated as CocoaError.userCancelled errors. See the catch statement below.
try await getRoute(origin: stop1, destination: stop2)
} catch let error as CocoaError where error.code == CocoaError.userCancelled {
// Handle a cancelation. This is propagated as a userCancelled error.
showAlert(title: "Canceled", message: "The route request was canceled by the user.")
} catch {
// Handle other (non-cancelation) errors.
showError(title: "Error calculating route", error: error)
} This description simplifies things a little and only scratches the surface of what Tasks can do. I’d encourage you to watch Apple’s sessions on Swift Concurrency covering Tasks and TaskGroups to learn more. AGSJobs and cancelation AGSJobs also support cancelation, but they do not use AGSCancelable. Instead, each AGSJob exposes an NSProgress object and is cancelled by calling the NSProgress’s cancel() method. As a result, AGSJob.start() doesn’t return an AGSCancelable, and so Xcode can (and does) create a virtual wrapper. If you need to cancel AGSJobs, we provide an additional file of AGSJob subclass async wrappers which supercede the ones Xcode generates. Unfortunately, these can’t be delivered through the Swift Package Manager without conflicting with the Xcode generated wrappers, so you have to explicitly add the AsyncCancelableJobWrappers.swift file to your project. Adding this file stops Xcode creating its own wrappers. If for some reason you don’t need to cancel AGSJobs, you could skip our custom wrappers and just use the Xcode generated ones, but your users would probably prefer you let them cancel long running jobs. Take it for a spin If you’ve been chomping at the bit to adopt async/await in your Runtime app, we encourage you to take the repo for a spin. See the instructions for more details on how to bring it into your project. The repo also includes an example app which shows how to cancel long running Runtime tasks and jobs, how to animate the map view without waiting for the animation to complete, and various other Swift Concurrency tips and tricks. Just bring your own API Key, scoped to Routing and Geocoding, and set it in the example’s AppDelegate. Let us know how you like the wrappers and if you're planning on using them in your own code. I've already started writing demos with them and I'm finding it hard to switch back. We anticipate that interest in Swift Concurrency will skyrocket once Xcode 13.2 is released and you'll be able to target all the way back to iOS 13.
... View more
11-22-2021
08:23 AM
|
1
|
0
|
2321
|
|
POST
|
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.
... View more
11-17-2021
12:17 PM
|
3
|
1
|
1385
|
|
POST
|
It doesn't look like layers are loaded, and they shouldn't be from the code you provided. When the offline map job completes, you are handed a reference to a new AGSMap that you can display in your AGSMapView. See How to download and display an on-demand offline map. You don't need to worry about individual geodatabases when you work with the OfflineMapTask. If you need to re-open the map later, you do so by referencing the download directory and create an AGSMobileMapPackage and read the first map from it. See How to access an offline map. See the Generate offline map sample for more. Hope that helps.
... View more
11-02-2021
08:22 AM
|
0
|
0
|
1400
|
|
POST
|
I'm not sure how you're converting UTM 1Q 433568 1978405 to Lat/Lon -19.164950 -96.148800 (which is in the pacific off the coast of Peru/Chile). I'm guessing you mean +19.164950 -96.148800 which is Veracruz, Mexico. The closest I can think is that you mean 14Q 433568 1978405 (and not 1Q), which is close to Mexico city and would translate to 17.892510°, -99.627141°. See this diagram and description of UTM Grid Zones.
... View more
10-26-2021
09:00 AM
|
2
|
1
|
1692
|
|
POST
|
Hi. You should not have the units in there. Use something like: let utmString = "1Q 433568 m 1978405 m"
let pt = AGSCoordinateFormatter.point(
fromUTMString: utmString.replacingOccurrences(of: "m", with: ""),
spatialReference: .wgs84(),
conversionMode: .latitudeBandIndicators
) That will give you a point at latitude 17.892510, longitude -177.627141, which matches what's returned here: https://awsm-tools.com/geo/utm-to-geographic Hope that helps.
... View more
10-25-2021
08:41 AM
|
0
|
3
|
1726
|
|
POST
|
Sorry Dave. The correct link to report the issue without a fully paid support trail is this: https://support.esri.com/en/report-bug
... View more
10-14-2021
01:58 PM
|
0
|
0
|
2300
|
|
POST
|
If you're interested in a full, exact geometry comparison, including Z and M values, you can use Geometry.IsEqual(). Depending on your use case, you might also find Geometry.Equals() helpful, which allows you to specify a tolerance.
... View more
10-14-2021
09:48 AM
|
0
|
0
|
1661
|
| 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 |