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
Before starting this tutorial:
-
You need an ArcGIS Location Platform or ArcGIS Online account.
-
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, get an access token and set the API key.
An API Key gives your app access to secure resources used in this tutorial.
-
Go to the Create an API key tutorial to obtain a new API key access token. Ensure that the following privilege is enabled: Location services > Basemaps > Basemap styles service. Copy the access token as it will be used in the next step.
-
In Xcode, in the Project Navigator, click AppDelegate.swift.
-
In the editor, set the
API
property on theKey AGS
with your access token.ArcGIS Runtime Environment AppDelegate.swiftUse dark colors for code blocks func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { AGSArcGISRuntimeEnvironment.apiKey = "YOUR_ACCESS_TOKEN" 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 AGS
from the web map's AGS
, and display it in your app's AGS
.
-
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
AGS
using thePortal Item AGS
that references ArcGIS Online, and the web map'sPortal item
. TheID portal
is used to create anItem AGS
that is displayed in the app'sMap AGS
.Map View ViewController.swiftUse dark colors for code blocks 30 31 39 40Change 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 AGS
. Then, add a UI
that reports the job's progress to your user.
-
Create a method named
user
and assign it theSelected Download Offline Map :() @objc
keyword. The@objc
method keyword exposes the method to Objective-C, a necessary step for using theUI
API.Bar 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
UI
and assign it toProgress View 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
UI
that allows your user to select an area of the map to take offline.Bar Button Item The code creates an array of three
UI
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,Bar Button Item "
, and tapping it performs the methodDownload Map Area" user
.Selected Download Offline Map :() ViewController.swiftUse dark colors for code blocks 44 45 46 54 55Add 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, callDid Load() setup
.U I() ViewController.swiftUse dark colors for code blocks 21 22 23 24 25 27 28Add 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
AGS
property to contain an offline area graphic.Graphics Overlay ViewController.swiftUse dark colors for code blocks 62Add line. private let graphicsOverlay = AGSGraphicsOverlay()
-
Define a private method to setup the graphics overlay. In the method, create an
AGS
to render graphics. Define a symbol for the renderer using anSimple Renderer AGS
and anSimple Fill Symbol AGS
.Simple Line Symbol ViewController.swiftUse dark colors for code blocks 61 62 77Add 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 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77Add 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, callDid Load() setup
.Graphics Overlay() ViewController.swiftUse dark colors for code blocks 21 22 23 24 25 26 27 29 30Add 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 87 88 89 90 91 96Add 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
AGS
property namedOffline Map Task offline
. The offline map task is used to create anMap Task AGS
, provided a few parameters, and a geometry.Generate Offline Map Job ViewController.swiftUse dark colors for code blocks 92 93 94 95 96Add line. private var temporaryDirectoryURL: URL { FileManager.default.temporaryDirectory .appendingPathComponent(ProcessInfo().globallyUniqueString) } private var offlineMapTask: AGSOfflineMapTask?
-
Create a private, optional
AGS
property namedGenerate Offline Map Job offline
. The offline map job handles transactions with the service to download an area of the web map offline.Map Job ViewController.swiftUse dark colors for code blocks 87 88 89 90 91 92 93 94 95 96 97 98Add 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
AGS
defining the offline area and aGeometry URL
at which to store the downloaded map.ViewController.swiftUse dark colors for code blocks 99 100Add line. Add line. Add line. private var offlineMapJob: AGSGenerateOfflineMapJob? private func downloadOfflineMap(with offlineArea: AGSGeometry, at downloadDirectory: URL) { }
-
Create an
AGS
by supplying the web map in the constructor.Offline Map Task ViewController.swiftUse dark colors for code blocks 99 100 101 102 105 106Add 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 101 102 103 104 105 111 112Add 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
AGS
. Use the offline map parameters to specify your preferences for the offline map. Finally, set the job'sGenerate Offline Map 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 101 102 103 104 105 106 107 108 109 134 135 136 137Add 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 Map :() ViewController.swiftUse dark colors for code blocks 87 88 89 91 92Add 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 87 88 89 90 91 93 94Add 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 87 88 89 90 91 92 93 95 96Add 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 87 88 89 90 91 92 93 94 95 97 98Add 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 Map With Offline Area :at Download Directory :() temporary
.Directory URL ViewController.swiftUse dark colors for code blocks 87 88 89 90 91 92 93 94 95 96 97 99 100Add 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: