Created
January 23, 2017 08:39
-
-
Save hhartz/e05fb938ab8a64ad56714eb0628a1885 to your computer and use it in GitHub Desktop.
Mapbox offline pack resume bug
This file contains hidden or 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. | |
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) { | |
// reload offline packs | |
MGLOfflineStorage.shared().addObserver(self, forKeyPath: "packs", options: [], context: nil) | |
MGLOfflineStorage.shared().reloadPacks() | |
} | |
override func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) { | |
startOfflinePackDownload() | |
} | |
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { | |
if keyPath == "packs" { | |
if let packs = MGLOfflineStorage.shared().packs, packs.count > 0 { | |
// try to resume any pending downloads | |
for pack in packs { | |
pack.addObserver(self, forKeyPath: "state", options: [], context: nil) | |
pack.requestProgress() | |
} | |
} | |
} | |
if keyPath == "state" { | |
if let pack = object as? MGLOfflinePack { | |
if pack.state == .inactive { | |
if let userInfo = NSKeyedUnarchiver.unarchiveObject(with: pack.context) as? [AnyHashable: Any], let packName = userInfo["name"] { | |
print("resuming \(packName)") | |
} | |
pack.resume() | |
} | |
pack.removeObserver(self, forKeyPath: "state") | |
} | |
} | |
} | |
deinit { | |
// Remove offline pack observers. | |
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] { | |
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