Skip to main content

Download an offline map

A newer version of the Maps SDK is available

This page uses v6.4.1 of the Mapbox Maps SDK. A newer version of the SDK is available. Learn about the latest version, v11.3.0, in the Maps SDK documentation.

See our mobile offline help page for more information about uses and limitations.

For a more comprehensive example that manages offline packs, see MBXOfflinePacksTableViewController.

ViewController
import Mapbox

class ViewController: UIViewController, MGLMapViewDelegate {
var mapView: MGLMapView!
var progressView: UIProgressView!

override func viewDidLoad() {
super.viewDidLoad()

mapView = MGLMapView(frame: view.bounds, styleURL: MGLStyle.darkStyleURL)
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.tintColor = .gray
mapView.delegate = self
view.addSubview(mapView)

mapView.setCenter(CLLocationCoordinate2D(latitude: 22.27933, longitude: 114.16281),
zoomLevel: 13, animated: false)

// Setup offline pack notification handlers.
NotificationCenter.default.addObserver(self, selector: #selector(offlinePackProgressDidChange), name: NSNotification.Name.MGLOfflinePackProgressChanged, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(offlinePackDidReceiveError), name: NSNotification.Name.MGLOfflinePackError, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(offlinePackDidReceiveMaximumAllowedMapboxTiles), name: NSNotification.Name.MGLOfflinePackMaximumMapboxTilesReached, object: nil)
}

func mapViewDidFinishLoadingMap(_ mapView: MGLMapView) {
// Start downloading tiles and resources for z13-14.
startOfflinePackDownload()
}

override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)

// When leaving this view controller, suspend offline downloads.
guard let packs = MGLOfflineStorage.shared.packs else { return }
for pack in packs {
if let userInfo = NSKeyedUnarchiver.unarchiveObject(with: pack.context) as? [String: String] {
print("Suspending download of offline pack: “\(userInfo["name"] ?? "unknown")”")
}
pack.suspend()
}
}

func startOfflinePackDownload() {
// Create a region that includes the current viewport and any tiles needed to view it when zoomed further in.
// Because tile count grows exponentially with the maximum zoom level, you should be conservative with your `toZoomLevel` setting.
let region = MGLTilePyramidOfflineRegion(styleURL: mapView.styleURL, bounds: mapView.visibleCoordinateBounds, fromZoomLevel: mapView.zoomLevel, toZoomLevel: 14)

// Store some data for identification purposes alongside the downloaded resources.
let userInfo = ["name": "My Offline Pack"]
let context = NSKeyedArchiver.archivedData(withRootObject: userInfo)

// Create and register an offline pack with the shared offline storage object.

MGLOfflineStorage.shared.addPack(for: region, withContext: context) { (pack, error) in
guard error == nil else {
// The pack couldn’t be created for some reason.
print("Error: \(error?.localizedDescription ?? "unknown error")")
return
}

// Start downloading.
pack!.resume()
}

}

// MARK: - MGLOfflinePack notification handlers

@objc func offlinePackProgressDidChange(notification: NSNotification) {
// Get the offline pack this notification is regarding,
// and the associated user info for the pack; in this case, `name = My Offline Pack`
if let pack = notification.object as? MGLOfflinePack,
let userInfo = NSKeyedUnarchiver.unarchiveObject(with: pack.context) as? [String: String] {
let progress = pack.progress
// or notification.userInfo![MGLOfflinePackProgressUserInfoKey]!.MGLOfflinePackProgressValue
let completedResources = progress.countOfResourcesCompleted
let expectedResources = progress.countOfResourcesExpected

// Calculate current progress percentage.
let progressPercentage = Float(completedResources) / Float(expectedResources)

// Setup the progress bar.
if progressView == nil {
progressView = UIProgressView(progressViewStyle: .default)
let frame = view.bounds.size
progressView.frame = CGRect(x: frame.width / 4, y: frame.height * 0.75, width: frame.width / 2, height: 10)
view.addSubview(progressView)
}

progressView.progress = progressPercentage

// If this pack has finished, print its size and resource count.
if completedResources == expectedResources {
let byteCount = ByteCountFormatter.string(fromByteCount: Int64(pack.progress.countOfBytesCompleted), countStyle: ByteCountFormatter.CountStyle.memory)
print("Offline pack “\(userInfo["name"] ?? "unknown")” completed: \(byteCount), \(completedResources) resources")
} else {
// Otherwise, print download/verification progress.
print("Offline pack “\(userInfo["name"] ?? "unknown")” has \(completedResources) of \(expectedResources) resources — \(String(format: "%.2f", progressPercentage * 100))%.")
}
}
}

@objc func offlinePackDidReceiveError(notification: NSNotification) {
if let pack = notification.object as? MGLOfflinePack,
let userInfo = NSKeyedUnarchiver.unarchiveObject(with: pack.context) as? [String: String],
let error = notification.userInfo?[MGLOfflinePackUserInfoKey.error] as? NSError {
print("Offline pack “\(userInfo["name"] ?? "unknown")” received error: \(error.localizedFailureReason ?? "unknown error")")
}
}

@objc func offlinePackDidReceiveMaximumAllowedMapboxTiles(notification: NSNotification) {
if let pack = notification.object as? MGLOfflinePack,
let userInfo = NSKeyedUnarchiver.unarchiveObject(with: pack.context) as? [String: String],
let maximumCount = (notification.userInfo?[MGLOfflinePackUserInfoKey.maximumCount] as AnyObject).uint64Value {
print("Offline pack “\(userInfo["name"] ?? "unknown")” reached limit of \(maximumCount) tiles.")
}
}

}