Learn how to download and display an offline map for a user-defined geographical area of a web map.
Offline maps allow users to continue working when network connectivity is poor or lost. If a web map is enabled for offline use, a user can request that ArcGIS generates an offline map for a specified geographic area of interest.
In this tutorial, you will download an offline map for an area of interest from the web map of the
stormwater network within Naperville, IL, USA
. You can then use this offline map without a network connection.Prerequisites
The following are required for this tutorial:
- An ArcGIS account to access your API keys. If you don't have an account, sign up for free.
- Your system meets the system requirements.
Steps
Open the Xcode project
-
To start the tutorial, complete the Display a web map tutorial or download and unzip the solution.
-
Open the
.xcodeproj
file in Xcode. -
If you downloaded the solution project, set your API key.
An API Key enables access to services, web maps, and web scenes hosted in ArcGIS Online.
-
Go to your developer dashboard to get your API key. For these tutorials, use your default API key. It is scoped to include all of the services demonstrated in the tutorials.
- In Xcode, in the Project Navigator, click AppDelegate.swift.
- In the editor, set the
APIKey
property on theAGSArcGISRuntime
with your API key.Environment
AppDelegate.swiftUse dark colors for code blocks func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Note: it is not best practice to store API keys in source code. // The API key is referenced here for the convenience of this tutorial. AGSArcGISRuntimeEnvironment.apiKey = "YOUR_API_KEY" return true }
-
Get the web map item ID
You can use ArcGIS tools to create and view web maps. Use the Map Viewer to identify the web map item ID. This item ID will be used later in the tutorial.
-
Go to the Naperville water network in the Map Viewer in ArcGIS Online. This web map displays a stormwater network within Naperville, Illinois, USA.
-
Make a note of the item ID at the end of the browser's URL.
The item ID should be 5a030a31e42841a89914bd7c5ecf4d8f.
Display the web map
You can display a web map using the web map's item ID. Create an AGSMap
from the web map's AGSPortal
, and display it in your app's AGSMap
.
-
In Xcode, in the Project Navigator, click ViewController.swift.
-
In the editor, modify the
setup
function. Provide the web map's item ID.Map() The code creates an
AGSPortal
using theItem AGSPortal
that references ArcGIS Online, and the web map'sitem
. TheI D portal
is used to create anItem AGSMap
that is displayed in the app'sAGSMap
.View ViewController.swiftUse dark colors for code blocks Change line Change line Change line Change line Change line Change line Change line private func setupMap() { let map = AGSMap( item: AGSPortalItem( portal: AGSPortal.arcGISOnline(withLoginRequired: false), itemID: "5a030a31e42841a89914bd7c5ecf4d8f" ) ) mapView.map = map }
Setup the UI
Setup your UI with a button that allows your users to select an area to download using an AGSGenerate
. Then, add a UIProgress
that reports the job's progress to your user.
-
Create a method named
user
and assign it theSelected Download Offline M a p: () @objc
keyword. The@objc
method keyword exposes the method to Objective-C, a necessary step for using theUIBar
API.Button Item
For now, leave the body of the method blank.ViewController.swiftUse dark colors for code blocks Add line. Add line. Add line. Add line. @objc func userSelectedDownloadOfflineMap(_ sender: UIBarButtonItem) { }
-
Create a private method to setup the app's UI. Within it, create a
UIProgress
and assign it toView navigation
.Item.title View ViewController.swiftUse dark colors for code blocks Add line. Add line. Add line. Add line. private func setupUI() { navigationItem.titleView = UIProgressView() }
-
Reveal the navigation controller's toolbar and create a
UIBar
that allows your user to select an area of the map to take offline.Button Item The code creates an array of three
UIBar
objects. The first and last objects reserve flexible space within the toolbar and the middle object is the download button. The button's title reads,Button Item "Download Map Area"
, and tapping it performs the methoduser
.Selected Download Offline M a p: () ViewController.swiftUse dark colors for code blocks Add line. Add line. Add line. Add line. Add line. Add line. Add line. private func setupUI() { navigationItem.titleView = UIProgressView() navigationController?.isToolbarHidden = false let items = [ UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil), UIBarButtonItem(title: "Download Map Area", style: .plain, target: self, action: #selector(userSelectedDownloadOfflineMap)), UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) ] setToolbarItems(items, animated: true) }
-
In the
view
method, callD i d Load() setup
.UI() ViewController.swiftUse dark colors for code blocks Add line. override func viewDidLoad() { super.viewDidLoad() setupMap() setupUI() }
-
In Xcode, in the Project Navigator, click Main.storyboard.
-
In the editor, select
View
, and in the menu bar, click Editor > Embed In > Navigation Controller.Controller Embedding
View
within a Navigation Controller will place a navigation bar at the top ofController View
. Inside the navigation bar you will find the progress view.Controller
Setup the offline area graphics
A graphics overlay is a container for graphics. A graphic is added to display the selected offline area on the map.
-
In Xcode, in the Project Navigator, click ViewController.swift.
-
In the editor, create a private
AGSGraphics
property to contain an offline area graphic.Overlay ViewController.swiftUse dark colors for code blocks Add line. private let graphicsOverlay = AGSGraphicsOverlay()
-
Define a private method to setup the graphics overlay. In the method, create an
AGSSimple
to render graphics. Define a symbol for the renderer using anRenderer AGSSimple
and anFill Symbol AGSSimple
.Line Symbol ViewController.swiftUse dark colors for code blocks Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. private let graphicsOverlay = AGSGraphicsOverlay() private func setupGraphicsOverlay() { graphicsOverlay.renderer = AGSSimpleRenderer( symbol: AGSSimpleFillSymbol( style: .solid, color: .clear, outline: AGSSimpleLineSymbol( style: .solid, color: .red, width: 3 ) ) ) mapView.graphicsOverlays.add(graphicsOverlay) }
-
Define a private method named that adds a graphic to the graphics overlay for the supplied
offline
geometry.Area ViewController.swiftUse dark colors for code blocks Add line. Add line. Add line. Add line. private let graphicsOverlay = AGSGraphicsOverlay() private func setupGraphicsOverlay() { graphicsOverlay.renderer = AGSSimpleRenderer( symbol: AGSSimpleFillSymbol( style: .solid, color: .clear, outline: AGSSimpleLineSymbol( style: .solid, color: .red, width: 3 ) ) ) mapView.graphicsOverlays.add(graphicsOverlay) } private func addGraphic(for offlineArea: AGSGeometry) { let graphic = AGSGraphic(geometry: offlineArea, symbol: nil) graphicsOverlay.graphics.add(graphic) }
-
In the
view
method, callD i d Load() setup
.Graphics Overlay() ViewController.swiftUse dark colors for code blocks Add line. override func viewDidLoad() { super.viewDidLoad() setupMap() setupUI() setupGraphicsOverlay() }
Download the map for offline use
When a user specifies an area of the web map to take offline, a number of tasks are performed.
-
Create a private computed
URL
property namedtemporary
. This computed property will generate a uniqueDirectory URL URL
at which to store the offline map on the device.ViewController.swiftUse dark colors for code blocks Add line. Add line. Add line. Add line. @objc func userSelectedDownloadOfflineMap(_ sender: UIBarButtonItem) { } private var temporaryDirectoryURL: URL { FileManager.default.temporaryDirectory .appendingPathComponent(ProcessInfo().globallyUniqueString) }
-
Create a private, optional
AGSOffline
property namedM a p Task offline
. The offline map task is used to create anM a p Task AGSGenerate
, provided a few parameters, and a geometry.Offline M a p Job ViewController.swiftUse dark colors for code blocks Add line. private var temporaryDirectoryURL: URL { FileManager.default.temporaryDirectory .appendingPathComponent(ProcessInfo().globallyUniqueString) } private var offlineMapTask: AGSOfflineMapTask?
-
Create a private, optional
AGSGenerate
property namedOffline M a p Job offline
. The offline map job handles transactions with the service to download an area of the web map offline.M a p Job ViewController.swiftUse dark colors for code blocks Add line. @objc func userSelectedDownloadOfflineMap(_ sender: UIBarButtonItem) { } private var temporaryDirectoryURL: URL { FileManager.default.temporaryDirectory .appendingPathComponent(ProcessInfo().globallyUniqueString) } private var offlineMapTask: AGSOfflineMapTask? private var offlineMapJob: AGSGenerateOfflineMapJob?
-
Define a private method to perform the download. This method receives two parameters, an
AGSGeometry
defining the offline area and aURL
at which to store the downloaded map.ViewController.swiftUse dark colors for code blocks Add line. Add line. Add line. private var offlineMapJob: AGSGenerateOfflineMapJob? private func downloadOfflineMap(with offlineArea: AGSGeometry, at downloadDirectory: URL) { }
-
Create an
AGSOffline
by supplying the web map in the constructor.M a p Task ViewController.swiftUse dark colors for code blocks Add line. Add line. private var offlineMapJob: AGSGenerateOfflineMapJob? private func downloadOfflineMap(with offlineArea: AGSGeometry, at downloadDirectory: URL) { guard let map = mapView.map else { return } offlineMapTask = AGSOfflineMapTask(onlineMap: map) }
-
Use the offline map task to generate offline map job parameters by supplying the offline area geometry.
ViewController.swiftUse dark colors for code blocks Add line. Add line. Add line. Add line. Add line. private func downloadOfflineMap(with offlineArea: AGSGeometry, at downloadDirectory: URL) { guard let map = mapView.map else { return } offlineMapTask = AGSOfflineMapTask(onlineMap: map) offlineMapTask?.defaultGenerateOfflineMapParameters(withAreaOfInterest: offlineArea) { [weak self] parameters, error in guard let self = self else { return } guard let offlineMapTask = self.offlineMapTask else { return } } }
-
Create the generate offline map job by supplying the offline map task with
AGSGenerate
. Use the offline map parameters to specify your preferences for the offline map. Finally, set the job'sOffline M a p Parameters progress
to the progress view'sobserved
property and start the job.Progress The offline map job is specified with two parameters.
- Set the
update
toMode .no
ensures that the offline map remains read-only.Updates - Set the
esri
toVector Tiles Download Option .use
to reduce the download size by stripping unused language characters from fonts.Reduced Fonts Service
ViewController.swiftUse dark colors for code blocks Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. private func downloadOfflineMap(with offlineArea: AGSGeometry, at downloadDirectory: URL) { guard let map = mapView.map else { return } offlineMapTask = AGSOfflineMapTask(onlineMap: map) offlineMapTask?.defaultGenerateOfflineMapParameters(withAreaOfInterest: offlineArea) { [weak self] parameters, error in guard let self = self else { return } guard let offlineMapTask = self.offlineMapTask else { return } if let parameters = parameters { parameters.updateMode = .noUpdates parameters.esriVectorTilesDownloadOption = .useReducedFontsService let job = offlineMapTask.generateOfflineMapJob(with: parameters, downloadDirectory: downloadDirectory) (self.navigationItem.titleView as! UIProgressView).observedProgress = job.progress var n = 0 job.start(statusHandler: { _ in while n < job.messages.count { print("Job message \(n): \(job.messages[n].message)") n += 1 } }, completion: { [weak self] result, error in guard let self = self else { return } if let result = result { self.mapView.map = result.offlineMap } else if let error = error { print("Error downloading the offline map: \(error)") return } }) self.offlineMapJob = job } else if let error = error { print("Error fetching default parameters for the area of interest: \(error.localizedDescription)") } } }
- Set the
-
In the
user
method, use the map view's visible area geometry to download the map.Selected Download Offline M a p: () ViewController.swiftUse dark colors for code blocks Add line. @objc func userSelectedDownloadOfflineMap(_ sender: UIBarButtonItem) { guard let offlineArea = mapView.visibleArea else { return } }
-
Disable the button after the user has selected an area of the map to take offline.
ViewController.swiftUse dark colors for code blocks Add line. @objc func userSelectedDownloadOfflineMap(_ sender: UIBarButtonItem) { guard let offlineArea = mapView.visibleArea else { return } sender.isEnabled = false }
-
Create a graphic for the selected offline area.
ViewController.swiftUse dark colors for code blocks Add line. @objc func userSelectedDownloadOfflineMap(_ sender: UIBarButtonItem) { guard let offlineArea = mapView.visibleArea else { return } sender.isEnabled = false addGraphic(for: offlineArea) }
-
Set the map view's view point for the selected offline area. Pad the area to slightly zoom the map view out, allowing you to visualize the context of the selected map area.
ViewController.swiftUse dark colors for code blocks Add line. @objc func userSelectedDownloadOfflineMap(_ sender: UIBarButtonItem) { guard let offlineArea = mapView.visibleArea else { return } sender.isEnabled = false addGraphic(for: offlineArea) mapView.setViewpointGeometry(offlineArea, padding: 25) }
-
Call
download
by supplying the selected offline area andOffline M a p With Offline Area: a t Download Directory: () temporary
.Directory URL ViewController.swiftUse dark colors for code blocks Add line. @objc func userSelectedDownloadOfflineMap(_ sender: UIBarButtonItem) { guard let offlineArea = mapView.visibleArea else { return } sender.isEnabled = false addGraphic(for: offlineArea) mapView.setViewpointGeometry(offlineArea, padding: 25) downloadOfflineMap(with: offlineArea, at: temporaryDirectoryURL) }
-
Press Command + R to run the app.
If you are using the Xcode simulator your system must meet these minimum requirements: macOS Big Sur 11.3, Xcode 13, iOS 13. If you are using a physical device, then refer to the system requirements.
You should see a web map of the Naperville water network in the map view, a progress bar embedded within the top navigation bar, and a Download Map Area button embedded within the bottom toolbar.
Tap the Download Map Area button to download the visible area of the web map, offline. Remove your network connection and you will still be able to pinch, drag, and double-tap the map view to explore this offline map.
What's next?
Learn how to use additional API features, ArcGIS location services, and ArcGIS tools in these tutorials: