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 using your ArcGIS Location Platform or ArcGIS Online account. 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 MainApp.swift.
-
In the Editor, set the
ArcGISEnvironment.apiKey
property on theArcGIS
with your access token.Environment MainApp.swiftUse dark colors for code blocks init() { ArcGISEnvironment.apiKey = APIKey("<#YOUR-ACCESS-TOKEN#>") }
-
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 Map
from the web map's PortalItem
, and display it in your app's MapView
.
-
In Xcode, in the Project Navigator, click ContentView.swift.
-
In the editor, modify the
map
variable. Provide the web map's item ID.The code creates an
PortalItem
using thePortal
that references ArcGIS Online, and the web map'sitem
. TheID portal
is used to create anItem Map
that is displayed in the app'sMapView
.ContentView.swiftUse dark colors for code blocks 103 104 105 106 108 109 110Change line @State private var map = Map( item: PortalItem( portal: .arcGISOnline(connection: .anonymous), id: PortalItem.ID("5a030a31e42841a89914bd7c5ecf4d8f")! ) )
-
Create a private class named
Model
of typeObservable
and add aObject @
variable of theState Object Model
to theContent
. Make theView Model
the@
. See the programming patterns page for more information on how to manage states.Main Actor ContentView.swiftUse dark colors for code blocks 16 17 18 23 24 25 27 28 29 30 31 32 33 34 35Add line. Add line. Add line. Add line. Add line. import SwiftUI import ArcGIS @MainActor private class Model: ObservableObject { } struct ContentView: View { @StateObject private var model = Model() @State private var map = Map( item: PortalItem( portal: .arcGISOnline(connection: .anonymous), id: PortalItem.ID("5a030a31e42841a89914bd7c5ecf4d8f")! ) )
-
In the
Model
, create a@
variable namedPublished offline
of typeMap Map
to store the output of the downloaded map.ContentView.swiftUse dark colors for code blocks 19 20 21 23 24Add line. @MainActor private class Model: ObservableObject { @Published private(set) var offlineMap: Map! }
-
In the
Content
, modify the map view to display the model's offline map or the online map. IfView offline
has no value, then the map view should display the online map.Map ContentView.swiftUse dark colors for code blocks 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 113 114 115 116Change line struct ContentView: View { @StateObject private var model = Model() @State private var map = Map( item: PortalItem( portal: .arcGISOnline(connection: .anonymous), id: PortalItem.ID("5a030a31e42841a89914bd7c5ecf4d8f")! ) ) var body: some View { MapView(map: model.offlineMap ?? map) } }
Specify an area of the web map to take offline
Specify an area of the web map to take offline using either an Envelope
or a Polygon
. Use views
to obtain data about the map view and an overlay
to indicate the area on the map to be downloaded.
-
In the
Content
, wrap the map view inside aView MapViewReader
and expose theMapViewProxy
in its closure. Name itmap
.View Map
provides operations that can be performed on the map view, such asView Proxy envelope(from
. For more information see Perform GeoView operations.View Rect :) ContentView.swiftUse dark colors for code blocks 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 117 118 119 120Add line. Add line. Add line. Add line. Add line. struct ContentView: View { @StateObject private var model = Model() @State private var map = Map( item: PortalItem( portal: .arcGISOnline(connection: .anonymous), id: PortalItem.ID("5a030a31e42841a89914bd7c5ecf4d8f")! ) ) var body: some View { MapViewReader { mapView in MapView(map: model.offlineMap ?? map) } } }
-
Wrap the map view reader inside a
Geometry
and expose theReader Geometry
in its closure. Name itProxy geometry
.Geometry
provides access to the size and coordinate space (for anchor resolution) of the views enclosed in the geometry reader.Proxy ContentView.swiftUse dark colors for code blocks 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 121 122 123 124Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. struct ContentView: View { @StateObject private var model = Model() @State private var map = Map( item: PortalItem( portal: .arcGISOnline(connection: .anonymous), id: PortalItem.ID("5a030a31e42841a89914bd7c5ecf4d8f")! ) ) var body: some View { GeometryReader { geometry in MapViewReader { mapView in MapView(map: model.offlineMap ?? map) } } } }
-
Add an
.overlay
modifier to the map view. The overlay contains a red rectangle that encompasses an area to be downloaded.ContentView.swiftUse dark colors for code blocks 112 113 114 115 116 117 118 119 126 127 128 129 130 131Add line. Add line. Add line. Add line. Add line. Add line. var body: some View { GeometryReader { geometry in MapViewReader { mapView in MapView(map: model.offlineMap ?? map) .overlay { Rectangle() .stroke(.red, lineWidth: 2) .padding(EdgeInsets(top: 60, leading: 20, bottom: 100, trailing: 20)) .opacity(model.offlineMap == nil ? 1 : 0) } } } }
Download and display the offline map
Generate and download an offline map for a specified area of interest using an asynchronous task. When complete, it will provide the offline map that can be displayed in a map view.
-
In the
Model
, create a@
Published GenerateOfflineMapJob
variable calledgenerate
and aOffline Map Job OfflineMapTask
variable calledoffline
. These objects will contain the job and task needed to perform the download function. Learn more about Tasks and jobs.Map Task ContentView.swiftUse dark colors for code blocks 19 20 21 22 23 26 27Add line. Add line. @MainActor private class Model: ObservableObject { @Published private(set) var offlineMap: Map! @Published private(set) var generateOfflineMapJob: GenerateOfflineMapJob! private var offlineMapTask: OfflineMapTask! }
-
Create a
@
Boolean variable calledPublished is
and set it to true. Create a variable calledGenerate Disabled is
and set it to false. These variables will be used to determine if the download button is enabled and if the completion alert is present.Showing Alert ContentView.swiftUse dark colors for code blocks 19 20 21 22 23 24 25 26 29 6Add line. Add line. @MainActor private class Model: ObservableObject { @Published private(set) var offlineMap: Map! @Published private(set) var generateOfflineMapJob: GenerateOfflineMapJob! private var offlineMapTask: OfflineMapTask! @Published private(set) var isGenerateDisabled = true @Published var isShowingAlert = false }
-
Create a private
URL
property namedtemporary
. This property will generate a uniqueDirectory URL URL
at which to store the offline map on the device.ContentView.swiftUse dark colors for code blocks 19 20 21 22 23 24 25 26 27 28 29 39 40Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. @MainActor private class Model: ObservableObject { @Published private(set) var offlineMap: Map! @Published private(set) var generateOfflineMapJob: GenerateOfflineMapJob! private var offlineMapTask: OfflineMapTask! @Published private(set) var isGenerateDisabled = true @Published var isShowingAlert = false private let temporaryDirectory: URL = { // swiftlint:disable:next force_try return try! FileManager.default.url( for: .itemReplacementDirectory, in: .userDomainMask, appropriateFor: FileManager.default.temporaryDirectory, create: true ) }() }
-
Create a
deinit
function to remove the temporary directory after the app closes.ContentView.swiftUse dark colors for code blocks 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 43 44Add line. Add line. Add line. @MainActor private class Model: ObservableObject { @Published private(set) var offlineMap: Map! @Published private(set) var generateOfflineMapJob: GenerateOfflineMapJob! private var offlineMapTask: OfflineMapTask! @Published private(set) var isGenerateDisabled = true @Published var isShowingAlert = false private let temporaryDirectory: URL = { // swiftlint:disable:next force_try return try! FileManager.default.url( for: .itemReplacementDirectory, in: .userDomainMask, appropriateFor: FileManager.default.temporaryDirectory, create: true ) }() deinit { try? FileManager.default.removeItem(at: temporaryDirectory) } }
-
Create an asynchronous function called
initialize
. This function loads the online map and creates aOffline Map Task(online Map :) OfflineMapTask
from it. Set theis
Boolean toGenerate Disabled false
.ContentView.swiftUse dark colors for code blocks 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 53 54Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. @MainActor private class Model: ObservableObject { @Published private(set) var offlineMap: Map! @Published private(set) var generateOfflineMapJob: GenerateOfflineMapJob! private var offlineMapTask: OfflineMapTask! @Published private(set) var isGenerateDisabled = true @Published var isShowingAlert = false private let temporaryDirectory: URL = { // swiftlint:disable:next force_try return try! FileManager.default.url( for: .itemReplacementDirectory, in: .userDomainMask, appropriateFor: FileManager.default.temporaryDirectory, create: true ) }() deinit { try? FileManager.default.removeItem(at: temporaryDirectory) } func initializeOfflineMapTask(onlineMap: Map) async { do { try await onlineMap.load() offlineMapTask = OfflineMapTask(onlineMap: onlineMap) isGenerateDisabled = false } catch { print(error) } } }
-
In the
Content
, call theView initialize
function in aOffline Map Task(online Map :) .task
modifier, passing in the onlinemap
. The task's function is called as the map view appears.ContentView.swiftUse dark colors for code blocks 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135Add line. Add line. Add line. struct ContentView: View { @StateObject private var model = Model() @State private var map = Map( item: PortalItem( portal: .arcGISOnline(connection: .anonymous), id: PortalItem.ID("5a030a31e42841a89914bd7c5ecf4d8f")! ) ) var body: some View { GeometryReader { geometry in MapViewReader { mapView in MapView(map: model.offlineMap ?? map) .task { await model.initializeOfflineMapTask(onlineMap: map) } .overlay { Rectangle() .stroke(.red, lineWidth: 2) .padding(EdgeInsets(top: 60, leading: 20, bottom: 100, trailing: 20)) .opacity(model.offlineMap == nil ? 1 : 0) } } } } }
-
In the
Model
, create an asynchronous function calledmake
. The function returnsGenerate Offline Map Parameters(area Of Interest :) GenerateOfflineMapParameters
, created usingoffline
and the area of interest.Map Task ContentView.swiftUse dark colors for code blocks 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 62 63Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. @MainActor private class Model: ObservableObject { @Published private(set) var offlineMap: Map! @Published private(set) var generateOfflineMapJob: GenerateOfflineMapJob! private var offlineMapTask: OfflineMapTask! @Published private(set) var isGenerateDisabled = true @Published var isShowingAlert = false private let temporaryDirectory: URL = { // swiftlint:disable:next force_try return try! FileManager.default.url( for: .itemReplacementDirectory, in: .userDomainMask, appropriateFor: FileManager.default.temporaryDirectory, create: true ) }() deinit { try? FileManager.default.removeItem(at: temporaryDirectory) } func initializeOfflineMapTask(onlineMap: Map) async { do { try await onlineMap.load() offlineMapTask = OfflineMapTask(onlineMap: onlineMap) isGenerateDisabled = false } catch { print(error) } } private func makeGenerateOfflineMapParameters(areaOfInterest: Envelope) async -> GenerateOfflineMapParameters? { do { return try await offlineMapTask.makeDefaultGenerateOfflineMapParameters(areaOfInterest: areaOfInterest) } catch { print(error) return nil } } }
-
Create an asynchronous function called
generate
. This function is called when the user initiates the download. Set theOffline Map(extent :) is
Boolean toGenerate Disabled true
and create default parameters for the given extent. If the parameters fail to create, set theis
Boolean toGenerate Disabled false
.ContentView.swiftUse dark colors for code blocks 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 64 65Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. @MainActor private class Model: ObservableObject { @Published private(set) var offlineMap: Map! @Published private(set) var generateOfflineMapJob: GenerateOfflineMapJob! private var offlineMapTask: OfflineMapTask! @Published private(set) var isGenerateDisabled = true @Published var isShowingAlert = false private let temporaryDirectory: URL = { // swiftlint:disable:next force_try return try! FileManager.default.url( for: .itemReplacementDirectory, in: .userDomainMask, appropriateFor: FileManager.default.temporaryDirectory, create: true ) }() deinit { try? FileManager.default.removeItem(at: temporaryDirectory) } func initializeOfflineMapTask(onlineMap: Map) async { do { try await onlineMap.load() offlineMapTask = OfflineMapTask(onlineMap: onlineMap) isGenerateDisabled = false } catch { print(error) } } private func makeGenerateOfflineMapParameters(areaOfInterest: Envelope) async -> GenerateOfflineMapParameters? { do { return try await offlineMapTask.makeDefaultGenerateOfflineMapParameters(areaOfInterest: areaOfInterest) } catch { print(error) return nil } } func generateOfflineMap(extent: Envelope) async { isGenerateDisabled = true guard let parameters = await makeGenerateOfflineMapParameters(areaOfInterest: extent) else { isGenerateDisabled = false return } } }
-
Create an offline map job using
make
and start the job.Generate Offline Map Job(parameters :download Directory :overrides :) ContentView.swiftUse dark colors for code blocks 63 64 65 66 67 68 69 75 69Add line. Add line. Add line. Add line. Add line. func generateOfflineMap(extent: Envelope) async { isGenerateDisabled = true guard let parameters = await makeGenerateOfflineMapParameters(areaOfInterest: extent) else { isGenerateDisabled = false return } generateOfflineMapJob = offlineMapTask.makeGenerateOfflineMapJob( parameters: parameters, downloadDirectory: temporaryDirectory ) generateOfflineMapJob.start() }
-
In a
defer
closure, ensure the job does not continue running by setting it tonil
and set theis
value according to whether or notGenerate Disabled offline
is instantiated.Map ContentView.swiftUse dark colors for code blocks 63 64 65 66 67 68 69 70 71 72 73 74 75 85 86Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. func generateOfflineMap(extent: Envelope) async { isGenerateDisabled = true guard let parameters = await makeGenerateOfflineMapParameters(areaOfInterest: extent) else { isGenerateDisabled = false return } generateOfflineMapJob = offlineMapTask.makeGenerateOfflineMapJob( parameters: parameters, downloadDirectory: temporaryDirectory ) generateOfflineMapJob.start() defer { generateOfflineMapJob = nil isGenerateDisabled = offlineMap != nil } do { } catch { print(error) } }
-
In the
do
closure, wait for theGenerateOfflineMapJob
to produce an output that contains the downloaded offline map. Assign the output to theoffline
property. UseMap EnvelopeBuilder
to expand the extent by0.8
. Set the offline map's initial viewpoint to this new envelope. Indicate that an alert is shown to the user.ContentView.swiftUse dark colors for code blocks 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 88 89 90 91 92 93Add line. Add line. Add line. Add line. Add line. Add line. func generateOfflineMap(extent: Envelope) async { isGenerateDisabled = true guard let parameters = await makeGenerateOfflineMapParameters(areaOfInterest: extent) else { isGenerateDisabled = false return } generateOfflineMapJob = offlineMapTask.makeGenerateOfflineMapJob( parameters: parameters, downloadDirectory: temporaryDirectory ) generateOfflineMapJob.start() defer { generateOfflineMapJob = nil isGenerateDisabled = offlineMap != nil } do { let output = try await generateOfflineMapJob.output offlineMap = output.offlineMap let builder = EnvelopeBuilder(envelope: extent) builder.expand(by: 0.8) offlineMap.initialViewpoint = Viewpoint(boundingGeometry: builder.toGeometry()) isShowingAlert = true } catch { print(error) } }
Setup the UI
Add a button to initiate the download, a Progress
to display the download progress, and use alert(
to indicate when the download is complete.
-
In the
Content
, add aView @
variable calledState is
and set it toGenerating Offline Map false
. This Boolean indicates whether an offline map is currently being generated.ContentView.swiftUse dark colors for code blocks 97 98 99 100 102 103 104 105 106 107 108 109 110Add line. struct ContentView: View { @StateObject private var model = Model() @State private var isGeneratingOfflineMap = false @State private var map = Map( item: PortalItem( portal: .arcGISOnline(connection: .anonymous), id: PortalItem.ID("5a030a31e42841a89914bd7c5ecf4d8f")! ) )
-
Add an
.interaction
modifier to the map view. WhenModes is
isGenerating Offline Map true
, the user should not be able to interact. Otherwise, allow the user to pan and zoom.ContentView.swiftUse dark colors for code blocks 112 113 114 115 116 117 118 119 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137Add line. var body: some View { GeometryReader { geometry in MapViewReader { mapView in MapView(map: model.offlineMap ?? map) .interactionModes(isGeneratingOfflineMap ? [] : [.pan, .zoom]) .task { await model.initializeOfflineMapTask(onlineMap: map) } .overlay { Rectangle() .stroke(.red, lineWidth: 2) .padding(EdgeInsets(top: 60, leading: 20, bottom: 100, trailing: 20)) .opacity(model.offlineMap == nil ? 1 : 0) } } } }
-
Add an
.overlay
modifier to the map view. Ifis
isGenerating Offline Map true
, create aProgress
to trackView model.generate
. Customize the progress view style, frame, and other attributes as required.Offline Map Job?.progress ContentView.swiftUse dark colors for code blocks 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 147 148 149 150 151 152Add 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. var body: some View { GeometryReader { geometry in MapViewReader { mapView in MapView(map: model.offlineMap ?? map) .interactionModes(isGeneratingOfflineMap ? [] : [.pan, .zoom]) .task { await model.initializeOfflineMapTask(onlineMap: map) } .overlay { Rectangle() .stroke(.red, lineWidth: 2) .padding(EdgeInsets(top: 60, leading: 20, bottom: 100, trailing: 20)) .opacity(model.offlineMap == nil ? 1 : 0) } .overlay { if isGeneratingOfflineMap, let progress = model.generateOfflineMapJob?.progress { VStack(spacing: 16) { ProgressView(progress) .progressViewStyle(.linear) .frame(maxWidth: 200) } .padding() .background(.regularMaterial) .clipShape(RoundedRectangle(cornerRadius: 15)) .shadow(radius: 3) } } } } }
-
Add an
.alert(
modifier to the map view. Present an alert to indicate that the offline map has finished generating. Use the model's_:is Presented :presenting :actions :message :) is
property to determine when to present the alert.Showing Alert ContentView.swiftUse dark colors for code blocks 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 153 154 155 156 157 158Add line. Add line. Add line. Add line. Add line. var body: some View { GeometryReader { geometry in MapViewReader { mapView in MapView(map: model.offlineMap ?? map) .interactionModes(isGeneratingOfflineMap ? [] : [.pan, .zoom]) .task { await model.initializeOfflineMapTask(onlineMap: map) } .overlay { Rectangle() .stroke(.red, lineWidth: 2) .padding(EdgeInsets(top: 60, leading: 20, bottom: 100, trailing: 20)) .opacity(model.offlineMap == nil ? 1 : 0) } .overlay { if isGeneratingOfflineMap, let progress = model.generateOfflineMapJob?.progress { VStack(spacing: 16) { ProgressView(progress) .progressViewStyle(.linear) .frame(maxWidth: 200) } .padding() .background(.regularMaterial) .clipShape(RoundedRectangle(cornerRadius: 15)) .shadow(radius: 3) } } .alert("Offline map generated", isPresented: $model.isShowingAlert) { Button("Done") { model.isShowingAlert = false } } } } }
-
Add a
Button
labeled "Download Map Area" to a toolbar at the bottom of the map view. When you click on the button, theis
property is set toGenerating Offline Map true
. The button is disabled if the offline map has been generated (model.is
) or if the offline map is being generated (Generated Disabled == true is
).Generating Offline Map == true ContentView.swiftUse dark colors for code blocks 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 165 166 167 168 169 170Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. var body: some View { GeometryReader { geometry in MapViewReader { mapView in MapView(map: model.offlineMap ?? map) .interactionModes(isGeneratingOfflineMap ? [] : [.pan, .zoom]) .task { await model.initializeOfflineMapTask(onlineMap: map) } .overlay { Rectangle() .stroke(.red, lineWidth: 2) .padding(EdgeInsets(top: 60, leading: 20, bottom: 100, trailing: 20)) .opacity(model.offlineMap == nil ? 1 : 0) } .overlay { if isGeneratingOfflineMap, let progress = model.generateOfflineMapJob?.progress { VStack(spacing: 16) { ProgressView(progress) .progressViewStyle(.linear) .frame(maxWidth: 200) } .padding() .background(.regularMaterial) .clipShape(RoundedRectangle(cornerRadius: 15)) .shadow(radius: 3) } } .alert("Offline map generated", isPresented: $model.isShowingAlert) { Button("Done") { model.isShowingAlert = false } } .toolbar { ToolbarItem(placement: .bottomBar) { Button("Download Map Area") { isGeneratingOfflineMap = true } .disabled(model.isGenerateDisabled || isGeneratingOfflineMap) } } } } }
-
Add a
.task
modifier to the button usingis
as its identifier. WhenGenerating Offline Map is
isGenerating Offline Map true
, create a frame from thegeometry
usingframe(in coordinate
. Use the frame to create an envelope in the map view usingSpace :) envelope(view
. Pass the resulting envelope into the model'sRect :) generate
function and setOffline Map(extent :) is
to false.Generating Offline Map ContentView.swiftUse dark colors for code blocks 154 155 156 157 158 159 160 161 162 177 178 179Add 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. .toolbar { ToolbarItem(placement: .bottomBar) { Button("Download Map Area") { isGeneratingOfflineMap = true } .disabled(model.isGenerateDisabled || isGeneratingOfflineMap) .task(id: isGeneratingOfflineMap) { guard isGeneratingOfflineMap else { return } let viewRect = geometry.frame(in: .local).inset( by: UIEdgeInsets( top: 60, left: geometry.safeAreaInsets.leading + 20, bottom: 100, right: -geometry.safeAreaInsets.trailing + 20 ) ) guard let extent = mapView.envelope(fromViewRect: viewRect) else { return } await model.generateOfflineMap(extent: extent) isGeneratingOfflineMap = false } } }
-
Press Command + R to run the app.
If you are using the Xcode simulator your system must meet these minimum requirements: macOS 14 (Sonoma), Xcode 16, iOS 18. 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 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. Once the download is complete, you will 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: