我如何正确使用 mapbox 的新 MGLOfflinePackDelegate?

How do i use mapbox's new MGLOfflinePackDelegate correctly?

我正在创建一个需要离线地图的应用程序。我正在使用 MapBox 进行测试,它从今天开始就支持离线地图(耶!)。我现在拥有的代码似乎可以用于下载地图,但是报告进度的委托永远不会触发,而且我不知道为什么会这样。

我的 mapView 有这个 class:

import UIKit
import Mapbox

class MapController: UIViewController, MGLMapViewDelegate,  UIPopoverPresentationControllerDelegate {

    @IBOutlet var mapView: MGLMapView!

    override func viewDidLoad() {
        super.viewDidLoad()       
        downloadIfNeeded()      
        mapView.maximumZoomLevel = 18
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func downloadIfNeeded() {
        MGLOfflineStorage.sharedOfflineStorage().getPacksWithCompletionHandler { (packs, error) in guard error == nil else {
                return
            }
            for pack in packs {
                let userInfo = NSKeyedUnarchiver.unarchiveObjectWithData(pack.context) as! [String: String]
                if userInfo["name"] == "London" {
                    // allready downloaded
                    return
                }
            }

            // define the download region
            let sw = CLLocationCoordinate2DMake(51.212120, 4.415906)
            let ne = CLLocationCoordinate2DMake(51.223781, 4.442401)

            let bounds = MGLCoordinateBounds(sw: sw, ne: ne)
            let region = MGLTilePyramidOfflineRegion(styleURL: MGLStyle.streetsStyleURL(), bounds: bounds, fromZoomLevel: 10, toZoomLevel: 12)

            let userInfo = ["name": "London"]
            let context = NSKeyedArchiver.archivedDataWithRootObject(userInfo)

            MGLOfflineStorage.sharedOfflineStorage().addPackForRegion(region, withContext: context) { (pack, error) in
                guard error == nil else {
                    return
                }

                // create popup window with delegate
                let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
                let downloadProgress: MapDownloadController = storyboard.instantiateViewControllerWithIdentifier("MapDownloadController") as! MapDownloadController
                downloadProgress.modalPresentationStyle = .Popover
                downloadProgress.preferredContentSize = CGSizeMake(300, 150)

                let popoverMapDownloadController = downloadProgress.popoverPresentationController
                popoverMapDownloadController?.permittedArrowDirections = .Any
                popoverMapDownloadController?.delegate = self
                popoverMapDownloadController?.sourceView = self.mapView
                popoverMapDownloadController?.sourceRect = CGRect(x: self.mapView.frame.midX, y: self.mapView.frame.midY, width: 1, height: 1)
                self.presentViewController(downloadProgress, animated: true, completion: nil)

                // set popup as delegate <----
                pack!.delegate = downloadProgress

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

而 MapDownloadController 是一个显示为弹出窗口的视图(参见上面的代码)并具有 MGLOfflinePackDelegate:

import UIKit
import Mapbox

class MapDownloadController: UIViewController, MGLOfflinePackDelegate {

    @IBOutlet var progress: UIProgressView!
    @IBOutlet var progressText: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func offlinePack(pack: MGLOfflinePack, progressDidChange progress: MGLOfflinePackProgress) {
        // this function is never called, but why? <----
        let completed = progress.countOfResourcesCompleted
        let expected = progress.countOfResourcesExpected
        let bytes = progress.countOfBytesCompleted

        let MB = bytes / 1024

        let str: String = "\(completed)/\(expected) voltooid (\(MB)MB)"
        progressText.text = str

        self.progress.setProgress(Float(completed) / Float(expected), animated: true)
    }

    func offlinePack(pack: MGLOfflinePack, didReceiveError error: NSError) {
        // neither is this one... <----
        let userInfo = NSKeyedUnarchiver.unarchiveObjectWithData(pack.context) as! [String: String]
        let strError = error.localizedFailureReason
    }

    func offlinePack(pack: MGLOfflinePack, didReceiveMaximumAllowedMapboxTiles maximumCount: UInt64) {
        // .. or this one  <----
        let userInfo = NSKeyedUnarchiver.unarchiveObjectWithData(pack.context) as! [String: String]
    }
}

这几乎都是从文档中提取的,那么为什么从未调用委托函数 (func offlinePack)?我确实使用断点进行了测试,所以我确定不是。尽管如此,仍会显示弹出窗口并下载该区域。 (通过观察网络流量和列出离线包的其他代码进行检查。)

(从 this GitHub issue 交叉发布。)

这是a bug in the SDK. The workaround is for the completion handler to assign the passed-in MGLOfflinePack object to an ivar or other strong reference in the surrounding MapDownloadController class (example).

这是 , using the current v3.2.0b1 example code 的一个极其简单的实现。预计此答案会很快过时,因为我们仍在开发 v3.2.0 版本。

import UIKit
import Mapbox

class ViewController: UIViewController, UIPopoverPresentationControllerDelegate, MGLOfflinePackDelegate {

    @IBOutlet var mapView: MGLMapView!

    // Array of offline packs for the delegate work around (and your UI, potentially)
    var offlinePacks = [MGLOfflinePack]()

    override func viewDidLoad() {
        super.viewDidLoad()

        mapView.maximumZoomLevel = 2
        downloadOffline()
    }

    func downloadOffline() {
        // Create a region that includes the current viewport and any tiles needed to view it when zoomed further in.
        let region = MGLTilePyramidOfflineRegion(styleURL: mapView.styleURL, bounds: mapView.visibleCoordinateBounds, fromZoomLevel: mapView.zoomLevel, toZoomLevel: mapView.maximumZoomLevel)

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

        // Create and register an offline pack with the shared offline storage object.
        MGLOfflineStorage.sharedOfflineStorage().addPackForRegion(region, withContext: context) { (pack, error) in
            guard error == nil else {
                print("The pack couldn’t be created for some reason.")
                return
            }

            // Set the pack’s delegate (assuming self conforms to the MGLOfflinePackDelegate protocol).
            pack!.delegate = self

            // Start downloading.
            pack!.resume()

            // Retain reference to pack to work around it being lost and not sending delegate messages
            self.offlinePacks.append(pack!)
        }
    }

    func offlinePack(pack: MGLOfflinePack, progressDidChange progress: MGLOfflinePackProgress) {
        let userInfo = NSKeyedUnarchiver.unarchiveObjectWithData(pack.context) as! [String: String]
        let completed = progress.countOfResourcesCompleted
        let expected = progress.countOfResourcesExpected
        print("Offline pack “\(userInfo["name"])” has downloaded \(completed) of \(expected) resources.")
    }

    func offlinePack(pack: MGLOfflinePack, didReceiveError error: NSError) {
        let userInfo = NSKeyedUnarchiver.unarchiveObjectWithData(pack.context) as! [String: String]
        print("Offline pack “\(userInfo["name"])” received error: \(error.localizedFailureReason)")
    }

    func offlinePack(pack: MGLOfflinePack, didReceiveMaximumAllowedMapboxTiles maximumCount: UInt64) {
        let userInfo = NSKeyedUnarchiver.unarchiveObjectWithData(pack.context) as! [String: String]
        print("Offline pack “\(userInfo["name"])” reached limit of \(maximumCount) tiles.")
    }

}