> For the complete documentation index, see [llms.txt](https://docs.mapbox.com/help/ja/llms.txt)

# Use Offline Maps in an iOS app

In this tutorial you will learn how to manage offline maps functionality in your iOS app using the [Mapbox Maps SDK for iOS](https://docs.mapbox.com/ios/maps/).

You will build an iOS app that displays a map and allows the user to trigger the download of three predefined **tile regions** for offline use. The app will also show download progress and status for each region.

Using your emulator or device, you will test the offline functionality by downloading regions and then using the app without an internet connection. The map will still render the downloaded regions, demonstrating offline functionality.

### What we'll cover:

-   Creating a SwiftUI view with a full screen map
-   Initializing the offline manager and downloading a style pack
-   Adding UI to download predefined tile regions
-   Tracking download progress and status
-   Testing offline functionality

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/ja/help/ja/assets/medias/tutorials--offline-maps-ios--download-abc8c7276c6415633e6ccd7b8c484cb6.mov).

## Prerequisites

To follow along with this tutorial, you will need:

-   **Xcode**: Version 16.2 or later
-   **Mapbox account**: [Sign up](https://account.mapbox.com/auth/signup/) for free or [log in](https://console.mapbox.com/) if you already have an account.
-   **A project with the Maps SDK installed**: Follow the [Getting Started with the Maps SDK for iOS](https://docs.mapbox.com/ios/maps/guides/install/) guide.
-   **Familiarity with iOS development**: Beginner experience with Swift, SwiftUI and iOS Development.

Before proceeding, make sure that you have:

-   Created a new iOS project in Xcode.
-   Installed the Mapbox Maps SDK for iOS using Swift Package Manager or CocoaPods.
-   Added your public access token to your project's `Info.plist` file.

## Create a full screen map

Start by creating a full screen map in your `ContentView.swift`. The camera options center the map on North America with a zoom level of 2 (showing the entire continent on a globe view).

```swift
import SwiftUI
import MapboxMaps

struct ContentView: View {
    var body: some View {
        Map(
            initialViewport: .camera(
                center: CLLocationCoordinate2D(
                    latitude: 39.5,
                    longitude: -98.0
                ),
                zoom: 2)
        ) 
        .ignoresSafeArea()
    }
}

#Preview {
    ContentView()
}
```

![iOS app showing a fullscreen map centered on North America](https://docs.mapbox.com/help/ja/assets/ideal-img/tutorials--offline-maps-ios--fullscreen-map.791a1a5.480.png)

For more details on setting up the Maps SDK for iOS, see the [installation guide](https://docs.mapbox.com/ios/maps/guides/install/).

With a full screen map set up, you are ready to add offline map functionality.

## Create an Offline Region Manager

Create an `OfflineRegionManager` class to handle the offline functionality. This class has three important methods for setting up offline maps:

1.  `ensureStylePackDownloaded()`: Downloads the style pack for offline use.
2.  `downloadRegion(_:)`: Initiates the download of a specified tile region.
3.  `clearAllRegions(_:)`: Removes all downloaded tile regions and clears the cache.

Each method is explained in detail below the code block.

```swift
import Foundation
import MapboxMaps

// offline region configuration struct
struct OfflineRegion {
    let id: String
    let name: String
    let bounds: CoordinateBounds
    
    // convert CoordinateBounds to Polygon
    var polygon: Polygon {
        let coordinates = [
            CLLocationCoordinate2D(latitude: bounds.southwest.latitude, longitude: bounds.southwest.longitude),
            CLLocationCoordinate2D(latitude: bounds.southwest.latitude, longitude: bounds.northeast.longitude),
            CLLocationCoordinate2D(latitude: bounds.northeast.latitude, longitude: bounds.northeast.longitude),
            CLLocationCoordinate2D(latitude: bounds.northeast.latitude, longitude: bounds.southwest.longitude),
            CLLocationCoordinate2D(latitude: bounds.southwest.latitude, longitude: bounds.southwest.longitude)
        ]
        return Polygon([coordinates])
    }
}

class OfflineRegionManager {

    // ensure the style pack for the Mapbox Standard Style is downloaded
    // the SDK will not re-download it if it's already present
    static func ensureStylePackDownloaded() {
        let offlineManager = OfflineManager()
        
        let stylePackLoadOptions = StylePackLoadOptions(
            glyphsRasterizationMode: .ideographsRasterizedLocally,
            metadata: ["name": "mapbox-standard-stylepack"],
            acceptExpired: false
        )
        
        offlineManager.loadStylePack(
            for: .standard,
            loadOptions: stylePackLoadOptions!
        ) { _ in } completion: { result in
            switch result {
            case let .success(stylePack):
                // Style pack download finishes successfully
                print("Downloaded style pack: \(stylePack)")
            case let .failure(error):
                // Handle error occurred during the style pack download
                if case StylePackError.canceled = error {
                    print("Style pack download cancelled")
                } else {
                    print("Style pack download failed: \(error)")
                }
            }
        }
    }
    
    static func downloadRegion(
        region: OfflineRegion,
        downloadingRegions: Set<String>,
        onDownloadingRegionsUpdate: @escaping (Set<String>) -> Void,
        onProgress: @escaping (String, Float) -> Void,
        onCompletion: @escaping (String, Result<TileRegion, Error>) -> Void
    ) {
        // Don't start if already downloading
        if downloadingRegions.contains(region.id) {
            return
        }
        
        let tileStore = TileStore.default
        let offlineManager = OfflineManager()
        
        // Create tileset descriptor
        let tilesetDescriptor = offlineManager.createTilesetDescriptor(
            for: TilesetDescriptorOptions(
                styleURI: .standard, // get tiles for the Mapbox Standard style
                zoomRange: 10...14,
                tilesets: []
            )
        )
        
        // Create load options using the region's polygon and metadata
        let loadOptions = TileRegionLoadOptions(
            geometry: .polygon(region.polygon),
            descriptors: [tilesetDescriptor],
            metadata: ["name": region.name],
            acceptExpired: true
        )!
        
        // Start downloading - notify UI to add to downloading set
        var updatedDownloadingRegions = downloadingRegions
        updatedDownloadingRegions.insert(region.id)
        onDownloadingRegionsUpdate(updatedDownloadingRegions)
        onProgress(region.id, 0.0)
        
        let _ = tileStore.loadTileRegion(
            forId: region.id,
            loadOptions: loadOptions
        ) { progress in
            let totalResources = max(progress.requiredResourceCount, 1)
            let progressValue = Float(progress.completedResourceCount) / Float(totalResources)
            onProgress(region.id, progressValue)
        } completion: { result in
            // Remove from downloading set and notify completion
            updatedDownloadingRegions.remove(region.id)
            onDownloadingRegionsUpdate(updatedDownloadingRegions)
            onCompletion(region.id, result)
        }
    }
    
    static func clearAllRegions(onCompletion: @escaping () -> Void) {
        let tileStore = TileStore.default
        
        // Get all tile regions from the store
        tileStore.allTileRegions { result in
            switch result {
            case .success(let tileRegions):
                // Remove each region found in the store
                for tileRegion in tileRegions {
                    tileStore.removeRegion(forId: tileRegion.id) { removeResult in
                        switch removeResult {
                        case .success:
                            print("Removed region: \(tileRegion.id)")
                        case .failure(let error):
                            print("Failed to remove region \(tileRegion.id): \(error)")
                        }
                    }
                }
                
                // Clear ambient cache after removing regions
                tileStore.clearAmbientCache { cacheResult in
                    switch cacheResult {
                    case .success(let bytes):
                        print("Cleared \(bytes) bytes from cache")
                    case .failure(let error):
                        print("Failed to clear cache: \(error)")
                    }
                    onCompletion()
                }
                
            case .failure(let error):
                print("Failed to get tile regions: \(error)")
                onCompletion()
            }
        }
    }
}
```

### Understanding the Style Pack

`ensureStylePackDownloaded()` downloads the **style pack** for the [Mapbox Standard style](https://docs.mapbox.com/map-styles/standard/guides/) by calling [`OfflineManager.loadStylePack()`](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxmaps/offlinemanager/). The style pack contains the map style and its resources (glyphs, fonts, etc.). The style and resources are usually loaded over the network when the map loads, so downloading a style pack is essential for rendering a map without internet access.

This method includes a check to avoid re-downloading the style pack if it is already present on the device.

In a later step, you will call `ensureStylePackDownloaded()` when the app launches to make sure the style pack is available for offline use.

### Understanding Tile Region Downloads

`downloadRegion(_:)` handles the download of specific regions of the map for offline use. It takes an `OfflineRegion` object as input, a custom struct defined for this tutorial which contains the region's ID, name, and bounding box in a convenient format.

The bounding box is the geographical area that will be downloaded for offline use, and is all you need to bootstrap the download workflow:

1.  **Initialize the `TileStore` and `OfflineManager`.** The [`TileStore`](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxcommon/tilestore/) manages the storage and retrieval of map tiles, while the [`OfflineManager`](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxmaps/offlinemanager/) provides methods for managing offline resources.
2.  **Create a [`TilesetDescriptor`](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxcommon/tilesetdescriptor/)** for the Mapbox Standard style using [`OfflineManager.createTilesetDescriptor(_:)`](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxmaps/offlinemanager/createtilesetdescriptor(for:)/). This descriptor defines the **style** and **zoom levels** for the tiles to be downloaded.
3.  **Create [`TileRegionLoadOptions`](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxcommon/tileregionloadoptions/)** using the region's bounding box and the tileset descriptor. This object specifies the area to download, the style, and metadata.
4.  **Call [`TileStore.loadTileRegion(_:)`](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxcommon/tilestore/loadtileregion(forid:loadoptions:progress:completion:)/)** to start the download. This method takes the region ID, load options, and two closures for tracking progress and handling completion.

The `downloadRegion(_:)` method also does the following:

-   checks if the region is already being downloaded to avoid duplicate downloads
-   updates state to track downloading regions and progress which can be used to update the UI
-   handles completion of the download, updating state and notifying the caller of success or failure

In a later step, you will set up options to pass to this method when the user initiates a download.

### Understanding Clearing Downloads

`clearAllRegions(onCompletion:)` removes all downloaded tile regions using [`TileStore.removeRegion(_:)`](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxcommon/tilestore/removeregion(forid:completion:)/) and clears the ambient cache using [`TileStore.clearAmbientCache(_:)`](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxcommon/tilestore/clearambientcache(completion:)/). This is useful for freeing up storage space or resetting the offline state. In a real-world app, you might want to provide a button or setting to allow users to clear their offline downloads to save space using a similar approach.

---

With `OfflineRegionManager` set up, you can now initialize it in your app and add UI to download tile regions.

## Trigger the Style Pack Download on Launch

In your main app file (for example, `ios_offline_mapsApp.swift`), call `OfflineRegionManager.ensureStylePackDownloaded()` to ensure the style pack is downloaded when the app launches.

You can call this at any point during app initialization, but doing it in the `App` struct's initializer ensures it runs as soon as the app starts.

```swift
import SwiftUI

@main
struct ios_offline_mapsApp: App {
    // highlight-start
    init() {
        OfflineRegionManager.ensureStylePackDownloaded()
    }
    // highlight-end
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

```

Make sure your app builds and runs successfully. You won't see any visible changes yet, but the style pack will be downloaded in the background. The code snippet includes a `print()` statement in the completion handler of `loadStylePack` to confirm the download, but you can add more handling as needed.

## Add UI for downloading tile regions

Create a new view called `TileRegionDownloadView` to display a list of tile regions and provide UI to the user to trigger their downloads.

The code below creates `TileRegionDownloadView` and a `RegionRowView` for each region in the list. Each row shows the region name, download status, progress, and a button to start the download.

It also creates `TileRegionDownloadViewModel` which defines the three regions to download.

For now, the buttons will be non-functional, and there is no state management. Placeholder values are passed to the `RegionRowView` to represent download status and progress. You will wire up the state management in the next step.

> **Note: Defining bounding boxes**
> 
> The bounding boxes defined in the code snippet below were defined using the Mapbox [Location Helper](https://labs.mapbox.com/location-helper) tool, which helps you quickly find a bounding box using a map drawing interface.

```swift
import SwiftUI
import MapboxMaps
internal import Combine

// MARK: - View Model

class TileRegionDownloadViewModel: ObservableObject {
    @Published var downloadingRegions: Set<String> = []
    @Published var downloadProgress: [String: Float] = [:]
    @Published var refreshTrigger = false

    // define regions
    let regions = [
        OfflineRegion(
            id: "new-york-region",
            name: "New York",
            bounds: CoordinateBounds(
                southwest: CLLocationCoordinate2D(latitude:  40.48398, longitude: -74.28127),
                northeast: CLLocationCoordinate2D(latitude: 40.98701, longitude: -73.58442)
            )
        ),
        OfflineRegion(
            id: "london-region",
            name: "London",
            bounds: CoordinateBounds(
                southwest: CLLocationCoordinate2D(latitude: 51.4874, longitude: -0.1278),
                northeast: CLLocationCoordinate2D(latitude: 51.5174, longitude: -0.0978)
            )
        ),
        OfflineRegion(
            id: "paris-region",
            name: "Paris",
            bounds: CoordinateBounds(
                southwest: CLLocationCoordinate2D(latitude: 48.8366, longitude: 2.3522),
                northeast: CLLocationCoordinate2D(latitude: 48.8666, longitude: 2.3822)
            )
        )
    ]
}

// MARK: - Main View

struct TileRegionDownloadView: View {
    @Environment(\.dismiss) private var dismiss
    @StateObject private var viewModel = TileRegionDownloadViewModel()

    var body: some View {
        NavigationView {
            List {
                ForEach(viewModel.regions, id: \.id) { region in
                    RegionRowView(
                        region: region,
                        isDownloading: false,
                        progress: 0.0,
                        refreshTrigger: false,
                        onDownload: {
                            // handle download
                        }
                    )
                }
            }
            .navigationTitle("Offline Regions")
            .toolbarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .topBarLeading) {
                    Button("Done", role: .cancel) {
                        dismiss()
                    }
                }
                ToolbarItem(placement: .topBarTrailing) {
                    Button("Clear All", role: .destructive) {
                        // handle clear
                    }
                    .tint(.red)
                }
            }
        }
    }
}

// MARK: - Row View

struct RegionRowView: View {
    let region: OfflineRegion
    let isDownloading: Bool
    let progress: Float
    let refreshTrigger: Bool
    let onDownload: () -> Void

    @State private var isDownloaded = false
    @State private var sizeInMB: Double = 0.0

    var body: some View {
        HStack {
            VStack(alignment: .leading, spacing: 4) {
                Text(region.name)
                    .font(.headline)
                
                if isDownloaded {
                    Text("Downloaded • \(String(format: "%.1f", sizeInMB)) MB")
                        .font(.caption)
                        .foregroundColor(.green)
                } else if isDownloading {
                    Text("Downloading...")
                        .font(.caption)
                        .foregroundColor(.blue)
                } else {
                    Text("Not downloaded")
                        .font(.caption)
                        .foregroundColor(.gray)
                }
            }
            
            Spacer()
            
            if isDownloading {
                VStack {
                    ProgressView()
                        .scaleEffect(0.8)
                    Text("\(Int(progress * 100))%")
                        .font(.caption2)
                        .foregroundColor(.blue)
                }
            } else if isDownloaded {
                Image(systemName: "checkmark.circle.fill")
                    .foregroundColor(.green)
                    .font(.title2)
            } else {
                Button(action: onDownload) {
                    Text("Download")
                        .font(.caption)
                        .padding(.horizontal, 12)
                        .padding(.vertical, 6)
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .cornerRadius(6)
                }
            }
        }
        .padding(.vertical, 4)
        .onAppear {
            checkIfDownloaded()
        }
        .onChange(of: isDownloading) { downloading in
            if !downloading {
                checkIfDownloaded()
            }
        }
        .onChange(of: refreshTrigger) { _ in
            checkIfDownloaded()
        }
    }

    private func checkIfDownloaded() {
        let tileStore = TileStore.default
        tileStore.tileRegion(forId: region.id) { result in
            DispatchQueue.main.async {
                switch result {
                case .success(let tileRegion):
                    self.isDownloaded = true
                    self.sizeInMB = Double(tileRegion.completedResourceSize) / (1024 * 1024)
                case .failure(_):
                    self.isDownloaded = false
                    self.sizeInMB = 0.0
                }
            }
        }
    }
}

```

### Understand `TileRegionDownloadViewModel`

`TileRegionDownloadViewModel` is an [`ObservableObject`](https://developer.apple.com/documentation/combine/observableobject) that manages the state for the `TileRegionDownloadView`. It defines three predefined regions (New York, London, Paris) with their bounding boxes using [`CoordinateBounds`](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxmaps/coordinatebounds/).

In later steps, it will be expanded to track downloading regions and progress, and to handle the download and clear all options.

### Understand `TileRegionDownloadView`

`TileRegionDownloadViewModel` is a SwiftUI view that displays a list of tile regions and provides UI for downloading them. It uses a `NavigationView` with a `List` to show each region using the `RegionRowView`.

### Understand `RegionRowView`

`RegionRowView` is a view that displays a single row in the list representing a tile region. It displays the region name, download status, progress, and a button to start the download.

Notice that `RegionRowView` contains a function `checkIfDownloaded()` that checks the `TileStore` using `tileStore.tileRegion(forId: region.id)` to see if the region has already been downloaded. The [`TileRegion`](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxcommon/tileregion/) object returned from the store contains the size of the downloaded resources, which is displayed in the UI to remind the user of the storage impact of offline downloads.

### Add `TileRegionDownloadView` to `ContentView`

Next update `ContentView` to include `TileRegionDownloadView` by placing both `TileRegionDownloadView` and `Map` inside a `ZStack` so that the download UI can overlay the map. Add a button to toggle the visibility of `TileRegionDownloadView`.

```swift
import SwiftUI          // For the user interface
import MapboxMaps       // For map rendering and interaction
import CoreLocation

struct ContentView: View {
    // highlight-start
    @State private var showingDownloadView = false
    // highlight-end

    var body: some View {
        // highlight-start
        ZStack {
        // highlight-end
            // Map takes full screen as the base layer
            Map(
                initialViewport: .camera(
                    center: CLLocationCoordinate2D(
                        latitude: 39.5,
                        longitude: -98.0
                    ),
                    zoom: 2)
            )
            .ignoresSafeArea()
            
            // highlight-start
            // Download button overlay
            VStack(alignment: .trailing) {
                Button(action: {
                    showingDownloadView = true
                }) {
                    Label("Manage Offline Regions", systemImage: "arrow.down.circle.fill")
                        .padding()
                        .background(Color.blue)
                        .foregroundColor(.white)
                        .cornerRadius(10)
                        .shadow(radius: 3)
                }
                .padding()
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
        }
        .sheet(isPresented: $showingDownloadView) {
            TileRegionDownloadView()
        }
        // highlight-end
    }
}
```

With `TileRegionDownloadView` added to `ContentView`, you should see a "Manage Offline Regions" button in the top-right corner of the map. Tapping this button will present the `TileRegionDownloadView` as a modal sheet.

In the next step you will connect the UI to the methods in `OfflineRegionManager` to make the download buttons functional and track download progress in the UI.

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/ja/help/ja/assets/medias/tutorials--offline-maps-ios--tile-region-ui-db274be3c1240303a3ab8286ed072540.mov).

## Connect UI to `OfflineRegionManager`

To make the tile region download buttons functional and track download progress as state, you need to integrate the `OfflineRegionManager` methods into the `TileRegionDownloadViewModel` and update the UI based on the published properties.

First update `TileRegionDownloadViewModel` to include methods for downloading a region and clearing all downloads. These methods will call the corresponding methods in `OfflineRegionManager` and update the published properties to reflect the current state.

```swift
...
class TileRegionDownloadViewModel: ObservableObject {
    @Published var downloadingRegions: Set<String> = []
    @Published var downloadProgress: [String: Float] = [:]
    @Published var refreshTrigger = false

    // define regions
    let regions = [
        OfflineRegion(
            id: "new-york-region",
            name: "New York",
            bounds: CoordinateBounds(
                southwest: CLLocationCoordinate2D(latitude:  40.48398, longitude: -74.28127),
                northeast: CLLocationCoordinate2D(latitude: 40.98701, longitude: -73.58442)
            )
        ),
        OfflineRegion(
            id: "london-region",
            name: "London",
            bounds: CoordinateBounds(
                southwest: CLLocationCoordinate2D(latitude: 51.4874, longitude: -0.1278),
                northeast: CLLocationCoordinate2D(latitude: 51.5174, longitude: -0.0978)
            )
        ),
        OfflineRegion(
            id: "paris-region",
            name: "Paris",
            bounds: CoordinateBounds(
                southwest: CLLocationCoordinate2D(latitude: 48.8366, longitude: 2.3522),
                northeast: CLLocationCoordinate2D(latitude: 48.8666, longitude: 2.3822)
            )
        )
    ]

    // highlight-start
    func downloadRegion(region: OfflineRegion) {
        OfflineRegionManager.downloadRegion(
            region: region,
            downloadingRegions: downloadingRegions,
            onDownloadingRegionsUpdate: { [weak self] updatedSet in
                DispatchQueue.main.async {
                    self?.downloadingRegions = updatedSet
                }
            },
            onProgress: { [weak self] regionId, progress in
                DispatchQueue.main.async {
                    self?.downloadProgress[regionId] = progress
                }
            },
            onCompletion: { [weak self] regionId, result in
                DispatchQueue.main.async {
                    self?.downloadProgress.removeValue(forKey: regionId)
                    
                    switch result {
                    case .success(let tileRegion):
                        print("Downloaded \(region.name): \(tileRegion.completedResourceSize) bytes")
                    case .failure(let error):
                        print("Failed to download \(region.name): \(error)")
                    }
                }
            }
        )
    }

    func clearAllRegions() {
        OfflineRegionManager.clearAllRegions { [weak self] in
            DispatchQueue.main.async {
                self?.downloadingRegions.removeAll()
                self?.downloadProgress.removeAll()
                self?.refreshTrigger.toggle()
            }
        }
    }
    // highlight-end
}

...

```

Next, update `TileRegionDownloadView` to call the new methods in the view model when the user taps the download button or the clear all button, and to pass state from the model into `RegionRowView`.

```swift
...
struct TileRegionDownloadView: View {
    @Environment(\.dismiss) private var dismiss
    @StateObject private var viewModel = TileRegionDownloadViewModel()

    var body: some View {
        NavigationView {
            List {
                ForEach(viewModel.regions, id: \.id) { region in
                    RegionRowView(
                        // highlight-start
                        region: region,
                        isDownloading: viewModel.downloadingRegions.contains(region.id),
                        progress: viewModel.downloadProgress[region.id] ?? 0.0,
                        refreshTrigger: viewModel.refreshTrigger,
                        onDownload: {
                            viewModel.downloadRegion(region: region)
                        }
                        // highlight-end
                    )
                }
            }
            .navigationTitle("Offline Regions")
            .toolbarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .topBarLeading) {
                    Button("Done", role: .cancel) {
                        dismiss()
                    }
                }
                ToolbarItem(placement: .topBarTrailing) {
                    Button("Clear All", role: .destructive) {
                        // highlight-start
                        viewModel.clearAllRegions()
                        // highlight-end
                    }
                    .tint(.red)
                }
            }
        }
    }
}
...

```

With these changes, the download buttons in `TileRegionDownloadView` will now be functional. Tapping a download button will start the download of the corresponding tile region, and the UI will update to show download progress. The "Clear All" button will remove all downloaded regions and clear the cache.

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/ja/help/ja/assets/medias/tutorials--offline-maps-ios--download-abc8c7276c6415633e6ccd7b8c484cb6.mov).

To recap, when you tap the download button for a region:

1.  `TileRegionDownloadView` calls `viewModel.downloadRegion(region:)`
2.  `TileRegionDownloadViewModel.downloadRegion(region:)` calls `OfflineRegionManager.downloadRegion(...)`
3.  `OfflineRegionManager.downloadRegion(...)` creates a `TilesetDescriptor`, builds `TileRegionLoadOptions`, and calls `TileStore.loadTileRegion(_:)` to trigger the download. It also calls the provided closures to update progress and completion status.

No additional steps are necessary to make the map display the downloaded regions. The Maps SDK for iOS automatically checks the `TileStore` for offline tiles when rendering the map, so once a region is downloaded, it will be available for offline viewing.

In the next step, you will test the offline functionality by downloading regions and then using the app without an internet connection.

## Test offline functionality

To test that your offline maps are working:

1.  **Download regions**: Run your app and download all three tile regions
    
2.  **Disconnect internet**: Turn off WiFi and cellular data on your device. If you are using the iOS Simulator, you can simulate offline mode by disabling the network on the machine running the simulator.
    
3.  **Test the map outside of the downloaded regions**: Pan and zoom the map to areas outside of the downloaded regions (New York, London, Paris). As you zoom in from zoom level 2 (looking at the globe), you will notice that map features do not load since you are outside the downloaded regions and do not have network access.
    

In this recording, the user zooms in on Washington, D.C. with the simulator's network disabled, demonstrating that no map tiles load since the area is not available for offline use.

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/ja/help/ja/assets/medias/tutorials--offline-maps-ios--washington-offline-fe0f97cdd8245d5b912ff609a132228c.mov).

4.  **Test the map in the downloaded regions**: Pan and zoom the map, moving towards the areas you downloaded (New York, London, Paris). As you zoom in from zoom level 2 (looking at the globe), you will notice that map features do not load since you are outside the downloaded regions and do not have network access. Once you zoom into one of the downloaded regions (between zoom levels 10-14), the map tiles should load and display correctly, even without an internet connection.

If you have viewed certain areas of the map while online, those tiles may be cached and could appear even when offline.

You can remove all cached tiles and start with a clean cache by choosing **Device > Erase All Content and Settings** in the iOS Simulator menu. This will reset the simulator to its factory settings, removing any cached data. Re-run your app, download the regions again, disable the network, and then test the offline functionality.

In this recording, the user zooms in on New York City with the simulator's network disabled. Notice that map data does not appear until zoom level 10, which is the minimum zoom level specified in the code for the downloaded regions. Between zoom levels 10-14, the map tiles load correctly from the offline tilestore.

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/ja/help/ja/assets/medias/tutorials--offline-maps-ios--nyc-offline-82408a8c307c209719a7a36c78e92ac5.mov).

Congratulations on successfully implementing offline maps in your iOS app!

> **Related content (related): [Download the final code](https://github.com/mapbox/tutorials/)**
> 
> The full source code for this tutorial is available on GitHub. You can download the final code and run it in Xcode to see the complete offline maps functionality in action.

## Next Steps

Congratulations on completing this tutorial! You have successfully implemented offline maps functionality for predefined regions in your iOS app using the Mapbox Maps SDK for iOS.

### What we covered

-   Created a full screen map using the Maps SDK for iOS
-   Initialized the offline manager and downloaded a style pack
-   Added UI to download predefined tile regions
-   Tracked download progress and status and updated the UI based on state
-   Tested offline functionality by downloading regions and using the app without an internet connection

### Things to try

With this minimal offline maps implementation complete, here are some ideas for further exploration:

-   **Change the Regions and Zoom Levels**: Change the predefined regions and zoom levels to suit your app's needs. You can define different cities or areas based on your target audience. Remember that specifying larger areas or higher zoom levels will increase the download and storage size.
-   **Add user-defined regions**: You could allow users to specify areas of interest for offline use by drawing on the map or entering coordinates.
-   **Implement automatic region management**: Add logic to automatically manage offline regions based on user behavior, such as downloading regions they visit often and removing old ones.
-   **Enhance the UI**: Improve the user interface for managing offline maps, such as adding visual indicators for downloaded regions on the map, or providing improved UI for downloading and managing regions.
-   **Handle errors and edge cases**: Implement more robust error handling for network issues, storage limitations, and other potential problems that may arise during downloads.

### Learn more about Offline Maps

> **Related content (related): [Maps SDK for iOS Offline documentation](https://docs.mapbox.com/ios/maps/guides/offline/)**
> 
> Read the full offline guide for the Maps SDK for iOS, which covers more advanced topics such as managing offline regions, handling errors, and optimizing storage.

> **Related content (related): [Use OfflineManager and TileStore to download a region](https://docs.mapbox.com/ios/maps/examples/offline-manager/)**
> 
> Try the offline example in the Maps SDK for iOS **examples app**, which provides an alternate implementation of offline maps functionality.