-
-
Save boundsj/679d3c332e2a9c0a5df738e95f31ace9 to your computer and use it in GitHub Desktop.
Mapbox offline pack resume bug
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Source code for mapbox offline pack resume bug | |
// | |
// Adapted from https://www.mapbox.com/ios-sdk/examples/offline-pack/. | |
// To download, use the shake gesture (ctrl-cmd-Z), and kill the app. When the app is resumed | |
// it will find a partial pack and resume it. Notice how the network and disk activity shows | |
// that a download is indeed ocurring, while there are no notifications posted on progress. | |
import UIKit | |
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(withVersion: 9)) | |
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. | |
MGLOfflineStorage.shared().addObserver(self, forKeyPath: "packs", options: [], context: nil) | |
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) | |
} | |
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) { | |
startOfflinePackDownload() | |
} | |
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { | |
if let keyPath = keyPath { | |
switch keyPath { | |
case "packs": | |
if let packs = MGLOfflineStorage.shared().packs { | |
for pack in packs { | |
if pack.state == .unknown { | |
pack.requestProgress() | |
} | |
} | |
} | |
return | |
default: | |
return | |
} | |
} | |
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) | |
} | |
deinit { | |
MGLOfflineStorage.shared().removeObserver(self, forKeyPath: "packs") | |
NotificationCenter.default.removeObserver(self) | |
} | |
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: 16) | |
// 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)") | |
return | |
} | |
// Start downloading. | |
pack!.resume() | |
} | |
} | |
// MARK: - MGLOfflinePack notification handlers | |
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] { | |
if pack.state == .inactive { | |
pack.resume() | |
return; | |
} | |
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"])” completed: \(byteCount), \(completedResources) resources") | |
} else { | |
// Otherwise, print download/verification progress. | |
print("Offline pack “\(userInfo["name"])” has \(completedResources) of \(expectedResources) resources — \(progressPercentage * 100)%.") | |
} | |
} | |
} | |
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?[MGLOfflinePackErrorUserInfoKey] as? Error { | |
print("Offline pack “\(userInfo["name"])” received error: \(error.localizedDescription)") | |
} | |
} | |
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?[MGLOfflinePackMaximumCountUserInfoKey] as? UInt64 { | |
print("Offline pack “\(userInfo["name"])” reached limit of \(maximumCount) tiles.") | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment