Display an offline map (on-demand)

Learn how to download and display an offline map for a user-defined geographical area of a web map.

display offline map on demand

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:

  1. You need an ArcGIS Location Platform or ArcGIS Online account.

  2. Your system meets the system requirements.

Steps

Open the Xcode project

  1. To start the tutorial, complete the Display a web map tutorial or download and unzip the solution.

  2. Open the .xcodeproj file in Xcode.

  3. If you downloaded the solution, get an access token and set the API key.

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.

  1. Go to the Naperville water network in the Map Viewer in ArcGIS Online. This web map displays a stormwater network within Naperville, Illinois, USA.

  2. Make a note of the item ID at the end of the browser's URL.

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.

  1. In Xcode, in the Project Navigator, click ContentView.swift.

  2. In the editor, modify the map variable. Provide the web map's item ID.

    ContentView.swift
    Use dark colors for code blocks
    103 104 105 106 107 108 109 110
    Change line
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
        @State private var map = Map(
            item: PortalItem(
                portal: .arcGISOnline(connection: .anonymous),
    
                id: PortalItem.ID("5a030a31e42841a89914bd7c5ecf4d8f")!
    
            )
        )
    
  3. Create a private class named Model of type ObservableObject and add a @StateObject variable of the Model to the ContentView. Make the Model the @MainActor. See the programming patterns page for more information on how to manage states.

    ContentView.swift
    Use dark colors for code blocks
    16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
    Add line.Add line.Add line.Add line.Add line.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    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")!
    
            )
        )
    
  4. In the Model, create a @Published variable named offlineMap of type Map to store the output of the downloaded map.

    ContentView.swift
    Use dark colors for code blocks
    19 20 21 22 23 24
    Add line.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    @MainActor
    private class Model: ObservableObject {
    
        @Published private(set) var offlineMap: Map!
    
    }
    
  5. In the ContentView, modify the map view to display the model's offline map or the online map. If offlineMap has no value, then the map view should display the online map.

    ContentView.swift
    Use 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
    Change line
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    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.

  1. In the ContentView, wrap the map view inside a MapViewReader and expose the MapViewProxy in its closure. Name it mapView. MapViewProxy provides operations that can be performed on the map view, such as envelope(fromViewRect:). For more information see Perform GeoView operations.

    ContentView.swift
    Use 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 118 119 120
    Add line.Add line.Add line.Add line.Add line.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    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)
    
                }
    
        }
    
    }
  2. Wrap the map view reader inside a GeometryReader and expose the GeometryProxy in its closure. Name it geometry. GeometryProxy provides access to the size and coordinate space (for anchor resolution) of the views enclosed in the geometry reader.

    ContentView.swift
    Use 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 118 119 120 121 122 123 124
    Add line.Add line.Add line.Add line.Add line.Add line.Add line.Add line.Add line.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    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)
    
                }
    
            }
    
        }
    
    }
  3. Add an .overlay modifier to the map view. The overlay contains a red rectangle that encompasses an area to be downloaded.

    ContentView.swift
    Use 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
    Add line.Add line.Add line.Add line.Add line.Add line.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
        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.

  1. In the Model, create a @Published GenerateOfflineMapJob variable called generateOfflineMapJob and a OfflineMapTask variable called offlineMapTask. These objects will contain the job and task needed to perform the download function. Learn more about Tasks and jobs.

    ContentView.swift
    Use dark colors for code blocks
    19 20 21 22 23 24 25 26 27
    Add line.Add line.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    @MainActor
    private class Model: ObservableObject {
    
        @Published private(set) var offlineMap: Map!
    
        @Published private(set) var generateOfflineMapJob: GenerateOfflineMapJob!
        private var offlineMapTask: OfflineMapTask!
    
    }
    
  2. Create a @Published Boolean variable called isGenerateDisabled and set it to true. Create a variable called isShowingAlert 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.

    ContentView.swift
    Use dark colors for code blocks
    19 20 21 22 23 24 25 26 27 28 29 6
    Add line.Add line.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    @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
    
    }
    
  3. Create a private URL property named temporaryDirectoryURL. This property will generate a unique URL at which to store the offline map on the device.

    ContentView.swift
    Use 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
    Add line.Add line.Add line.Add line.Add line.Add line.Add line.Add line.Add line.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    @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
            )
        }()
    
    }
    
  4. Create a deinit function to remove the temporary directory after the app closes.

    ContentView.swift
    Use 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
    Add line.Add line.Add line.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    @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)
        }
    
    }
    
  5. Create an asynchronous function called initializeOfflineMapTask(onlineMap:). This function loads the online map and creates a OfflineMapTask from it. Set the isGenerateDisabled Boolean to false.

    ContentView.swift
    Use 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
    Add line.Add line.Add line.Add line.Add line.Add line.Add line.Add line.Add line.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    @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)
            }
        }
    
    }
    
  6. In the ContentView, call the initializeOfflineMapTask(onlineMap:) function in a .task modifier, passing in the online map. The task's function is called as the map view appears.

    ContentView.swift
    Use 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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
    Add line.Add line.Add line.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    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)
                        }
    
                }
    
            }
    
        }
    
    }
  7. In the Model, create an asynchronous function called makeGenerateOfflineMapParameters(areaOfInterest:). The function returns GenerateOfflineMapParameters, created using offlineMapTask and the area of interest.

    ContentView.swift
    Use 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 63
    Add line.Add line.Add line.Add line.Add line.Add line.Add line.Add line.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    @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
            }
        }
    
    }
    
  8. Create an asynchronous function called generateOfflineMap(extent:). This function is called when the user initiates the download. Set the isGenerateDisabled Boolean to true and create default parameters for the given extent. If the parameters fail to create, set the isGenerateDisabled Boolean to false.

    ContentView.swift
    Use 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 63 64 65 66 67 68 69 63 64 65
    Add line.Add line.Add line.Add line.Add line.Add line.Add line.Add line.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    @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
            }
    
        }
    
    }
    
  9. Create an offline map job using makeGenerateOfflineMapJob(parameters:downloadDirectory:overrides:) and start the job.

    ContentView.swift
    Use dark colors for code blocks
    63 64 65 66 67 68 69 70 71 72 73 74 75 69
    Add line.Add line.Add line.Add line.Add line.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
        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()
    
        }
    
  10. In a defer closure, ensure the job does not continue running by setting it to nil and set the isGenerateDisabled value according to whether or not offlineMap is instantiated.

    ContentView.swift
    Use dark colors for code blocks
    63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
    Add line.Add line.Add line.Add line.Add line.Add line.Add line.Add line.Add line.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
        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)
            }
    
        }
    
  11. In the do closure, wait for the GenerateOfflineMapJob to produce an output that contains the downloaded offline map. Assign the output to the offlineMap property. Use EnvelopeBuilder to expand the extent by 0.8. Set the offline map's initial viewpoint to this new envelope. Indicate that an alert is shown to the user.

    ContentView.swift
    Use dark colors for code blocks
    63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
    Add line.Add line.Add line.Add line.Add line.Add line.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
        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 ProgressView to display the download progress, and use alert(_:isPresented:presenting:actions:message:) to indicate when the download is complete.

  1. In the ContentView, add a @State variable called isGeneratingOfflineMap and set it to false. This Boolean indicates whether an offline map is currently being generated.

    ContentView.swift
    Use dark colors for code blocks
    97 98 99 100 101 102 103 104 105 106 107 108 109 110
    Add line.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    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")!
    
            )
        )
    
  2. Add an .interactionModes modifier to the map view. When isGeneratingOfflineMap is true, the user should not be able to interact. Otherwise, allow the user to pan and zoom.

    ContentView.swift
    Use 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
    Add line.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
        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)
                        }
    
                }
    
            }
    
        }
    
  3. Add an .overlay modifier to the map view. If isGeneratingOfflineMap is true, create a ProgressView to track model.generateOfflineMapJob?.progress. Customize the progress view style, frame, and other attributes as required.

    ContentView.swift
    Use 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
    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.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
        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)
                            }
                        }
    
                }
    
            }
    
        }
    
  4. Add an .alert(_:isPresented:presenting:actions:message:) modifier to the map view. Present an alert to indicate that the offline map has finished generating. Use the model's isShowingAlert property to determine when to present the alert.

    ContentView.swift
    Use 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 154 155 156 157 158
    Add line.Add line.Add line.Add line.Add line.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
        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
                            }
                        }
    
                }
    
            }
    
        }
    
  5. Add a Button labeled "Download Map Area" to a toolbar at the bottom of the map view. When you click on the button, the isGeneratingOfflineMap property is set to true. The button is disabled if the offline map has been generated (model.isGeneratedDisabled == true) or if the offline map is being generated (isGeneratingOfflineMap == true).

    ContentView.swift
    Use 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 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
    Add line.Add line.Add line.Add line.Add line.Add line.Add line.Add line.Add line.Add line.Add line.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
        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)
    
                            }
                        }
    
                }
    
            }
    
        }
    
  6. Add a .task modifier to the button using isGeneratingOfflineMap as its identifier. When isGeneratingOfflineMap is true, create a frame from the geometry using frame(in coordinateSpace:). Use the frame to create an envelope in the map view using envelope(viewRect:). Pass the resulting envelope into the model's generateOfflineMap(extent:) function and set isGeneratingOfflineMap to false.

    ContentView.swift
    Expand
    Use dark colors for code blocks
    154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
    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.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    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
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
                        .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
                                }
    
                            }
                        }
    
    Expand
  7. Press Command + R to run the app.

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:

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.