Skip to content

Instantly share code, notes, and snippets.

@boundsj
Forked from hhartz/ViewController.swift
Last active May 7, 2020 04:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save boundsj/679d3c332e2a9c0a5df738e95f31ace9 to your computer and use it in GitHub Desktop.
Save boundsj/679d3c332e2a9c0a5df738e95f31ace9 to your computer and use it in GitHub Desktop.
Mapbox offline pack resume bug
// 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