Execute function on startup - ios

I'm trying to develop an application for IOS using swift language that is a news for me. I want to fill a dictionary (tobaccoList) on the application startup. I have a csv file, so I take data from this file and than i fill the dictionary:
class DataManager{
var latitudes = Array<Double>()
var longitudes = Array<Double>()
var tobaccoList = Dictionary<Double, Tabacchino>()
init(){
if let url = NSURL(fileURLWithPath: "/Users/brunopistone/Developer/apptabacchi/LocationList_sorted.csv" , isDirectory: true) {
var error: NSErrorPointer = nil
if let csv = CSV(contentsOfURL: url, error: error) {
//put every tabbacchino in a Dictionary tobaccoList
let rows = csv.rows
let totalRows = rows.count
for var index = 1; index < totalRows; index++ {
let temp = csv.rows[index]
let tabacchino = Tabacchino(
name: temp["Name"]!, phone: temp["tnumber"]!, lat: NSString(string: temp["Latitude"]!).doubleValue, lon: NSString(string: temp["Longitude"]!).doubleValue
)
let keyGeo = NSString(string: temp["Latitude"]!).doubleValue
storeTobaccoShop(keyGeo, value: tabacchino)
var doubleLatitude = NSString(string: temp["Latitude"]!).doubleValue
var doubleLongitude = NSString(string: temp["Longitude"]!).doubleValue
storeLatitude(doubleLatitude)
storeLongitudes(doubleLongitude)
}
}
}
}
func storeTobaccoShop(key: Double, value: Tabacchino) {
self.tobaccoList[key] = value
}
In the viewController file of the home page i have:
class ViewController: UIViewController, CLLocationManagerDelegate {
let startFunction = DataManager()
let locationManager = CLLocationManager()
var latitude = Double()
var longitude = Double()
var tobaccoList = Dictionary<Double, Tabacchino>()
override func viewDidLoad() {
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
tobaccoList = startFunction.getTobaccoList()
}
In the home page, I have a button that calls another view, and i want to pass the dictionary to the other view in order to use it, so I use this method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "tobaccoListSegue"{
let viewList = segue.destinationViewController as! ViewList
viewList.tabacchini = tobaccoList
}
}
The problem is, when i click on the button in order to call viewList, the application fills again the dictionary. What i want is to fill the dictionary only when I open the application.
Please help me fix this thing. Thanks

Put this line
let startFunction = DataManager()
Inside viewdidload() method.

Related

Getting a nil in Swift 3?

I am creating an app where I have annotation view showing that when you click it shows on the DetailsViewController that annotation data. However, I get "Name", and "Address" data but for phone Number I am getting set as nil. So, if you guys can see my code & help me solve it, I will be appreciated it.
Here is my code:
import UIKit
import MapKit
protocol UserLocationDelegate {
func userLocation(latitude :Double, longitude :Double)
}
class NearMeMapViewController: ARViewController, ARDataSource, MKMapViewDelegate, CLLocationManagerDelegate {
var nearMeIndexSelected = NearMeIndexTitle ()
var locationManager : CLLocationManager!
var nearMeARAnnotations = [ARAnnotation]()
var nearMeRequests = [NearMeRequest]()
var delegate : UserLocationDelegate!
var place: Place?
override func viewDidLoad() {
super.viewDidLoad()
self.title = nearMeIndexSelected.indexTitle
self.locationManager = CLLocationManager ()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.distanceFilter = kCLHeadingFilterNone
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.dataSource = self
self.headingSmoothingFactor = 0.05
self.maxVisibleAnnotations = 30
getNearMeIndexSelectedLocation()
}
func getNearMeIndexSelectedLocation()
{
let nearMeRequest = MKLocalSearchRequest()
nearMeRequest.naturalLanguageQuery = nearMeIndexSelected.indexTitle
let nearMeregion = MKCoordinateRegionMakeWithDistance(self.locationManager.location!.coordinate, 250, 250)
nearMeRequest.region = nearMeregion
let nearMeSearch = MKLocalSearch(request: nearMeRequest)
nearMeSearch.start { (response : MKLocalSearchResponse?, error :Error?) in
for requestItem in (response?.mapItems)! {
let nearMeIndexRequest = NearMeRequest ()
nearMeIndexRequest.name = requestItem.name
nearMeIndexRequest.coordinate = requestItem.placemark.coordinate
nearMeIndexRequest.address = requestItem.placemark.addressDictionary?["FormattedAddressLines"] as! [String]
nearMeIndexRequest.street = requestItem.placemark.addressDictionary?["Street"] as! String!
nearMeIndexRequest.city = requestItem.placemark.addressDictionary?["City"] as! String
nearMeIndexRequest.state = requestItem.placemark.addressDictionary?["State"] as! String
nearMeIndexRequest.zip = requestItem.placemark.addressDictionary?["ZIP"] as! String
self.nearMeRequests.append(nearMeIndexRequest)
print(requestItem.placemark.name)
}
for nearMe in self.nearMeRequests {
let annotation = NearMeAnnotation(nearMeRequest: nearMe)
self.nearMeARAnnotations.append(annotation)
self.setAnnotations(self.nearMeARAnnotations)
}
}
}
func ar(_ arViewController: ARViewController, viewForAnnotation: ARAnnotation) -> ARAnnotationView {
let annotationView = NearMeARAnnotationView(annotation: viewForAnnotation)
// annotationView.delegate = self
annotationView.frame = CGRect(x: 0, y: 0, width: 150, height: 50)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.tapBlurButton(_:)))
annotationView.addGestureRecognizer(tapGesture)
return annotationView
}
func tapBlurButton(_ sender: UITapGestureRecognizer) {
if let annotationView = sender.view as? NearMeARAnnotationView {
if let detailsVc = storyboard?.instantiateViewController(withIdentifier: "DetailsViewController")
as? DetailsViewController {
detailsVc.annotation = annotationView.annotation
if let annotation = annotationView.annotation as? Place {
detailsVc.place = annotation
}
self.navigationController?.pushViewController(detailsVc, animated: true)
}
}
}
}
Just looking over your code quickly:
"\(nearMeAnnotation.nearMeRequest.phone)"
All the other ones have a forced unwrap, this one doesn't. Most probably the value is nil and since you ask for a string representation of a wrapped var that might really be nil sometimes.
I think you should use a default value everywhere instead of a forced unwrap, like:
"\(nearMeAnnotation.nearMeRequest.phone ?? "")"
but also:
"\(nearMeAnnotation.nearMeRequest.street ?? "") \(nearMeAnnotation.nearMeRequest.state ?? "") \(nearMeAnnotation.nearMeRequest.state ?? "") \(nearMeAnnotation.nearMeRequest.zip ?? "")"
With forced unwraps your application will crash if a certain value is not set. This could be handled more elegantly if they're really required, for example already in the constructor of your object. There's the root cause of the optionals you're seeing. In this case NearMeAnnotation.

swift: what is the correct way of working with current location

I am quite new to swift and IOS development.
I would like to ask experienced members what is the correct way of creating mapvie and tableview in one viewcontroller and populating them.
The logic of application is the following:
get current location of user
read plist file with POI coordinates
for each POI run a function wich calculates distance between user and point
populate table with data from plist file plus newly calculated data.
Both mapview and tableview are in the same viewcontroller.
in viewDidLoad I am getting users location.
in viewwillappear I am running functions to read plist file and calculate distances between POIs and user.
Everything is working but it is not stable ... sometimes it might show user's location but table will be empty. So I doubt that I am doing everything correctly. Also probably it is not correct to put both map and table inside one class?
Update
Here is the code:
import UIKit
import MapKit
import CoreLocation
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var theMapView: MKMapView!
#IBOutlet weak var tableView: UITableView!
var locationManager: CLLocationManager!
var branches = [Branch]()
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
self.locationManager.requestAlwaysAuthorization()
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
tableView.dataSource = self
tableView.delegate = self
locationManager.startUpdatingLocation()
locationManager.startUpdatingHeading()
//mapview setup to show user location
theMapView.delegate = self
theMapView.showsUserLocation = true
theMapView.mapType = MKMapType(rawValue: 0)!
theMapView.userTrackingMode = MKUserTrackingMode(rawValue: 2)!
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
readFromPlist()
}
//MARK: UITableView methods
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return branches.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: MapCustomCell = tableView.dequeueReusableCellWithIdentifier("mapCell") as! MapCustomCell
let brnch = branches[indexPath.row]
cell.mapSetupCell(brnch.cityName, AddressLabel: brnch.address, DistanceLabel: brnch.distance)
return cell
}
//MARK: FUNC TO CALCULATE DISTANCE
func calculateDistance (lat1 lat1: Double, lon1: Double, lat2: Double, lon2: Double) -> String {
return "100km"
}
func locationManager (manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let myCoordinates = locations.last
let myLat = myCoordinates!.coordinate.latitude
let myLong = myCoordinates!.coordinate.longitude
let myCoordinates2D = CLLocationCoordinate2DMake(myLat, myLong)
let myLatDelta = 0.10
let myLongDelta = 0.10
let mySpan = MKCoordinateSpanMake(myLatDelta, myLongDelta)
let myRegion = MKCoordinateRegion(center: myCoordinates2D, span: mySpan)
theMapView.setRegion(myRegion, animated: true)
let myAnno = MKPointAnnotation()
myAnno.coordinate = myCoordinates2D
self.locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("error:" + error.localizedDescription)
}
func readFromPlist() {
//read plist file to extract banks coordinates
let path = NSBundle.mainBundle().pathForResource("poi", ofType: "plist")
let POIarrays = NSArray(contentsOfFile: path!)
for arr in POIarrays! {
var ctName : String!
var brnchAddress : String!
var wrkngHours : String!
var lat : Double!
var long : Double!
ctName = arr.objectForKey("cityName")! as! String
brnchAddress = arr.objectForKey("address")! as! String
wrkngHours = arr.objectForKey("workingHours")! as! String
lat = Double(arr.objectForKey("latitude")! as! String)
long = Double(arr.objectForKey("longitude")! as! String)
let latitude: CLLocationDegrees = lat
let longitude : CLLocationDegrees = long
let bankLocation : CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude, longitude)
let annotation = MKPointAnnotation()
annotation.coordinate = bankLocation
annotation.title = bnkName
annotation.subtitle = brnchAddress
self.theMapView.addAnnotation(annotation)
let myLatitude = self.locationManager.location?.coordinate.latitude
let myLongitude = self.locationManager.location?.coordinate.longitude
if myLatitude != nil {
let dist = calculateDistance(lat1: latitude, lon1: longitude, lat2: myLatitude!, lon2: myLongitude!)
let b = Branch(cityName: ctName!, address: brnchAddress!, distance: dist)
branches.append(b)
}
}
}
}
Sometimes I got an error "error:The operation couldn’t be completed. (kCLErrorDomain error 0.)" and current location doesn't appear on my map.

Trying to run a PFQuery that will lead to only one result being returned however the parameters that I am envoking are not specific enough

I am trying to run a PFQuery in order to find specific results for an address stored in the server. However, when I try for a specific address I am always returned 0. The fields that this class possesses are objectId, startTime and title. I am unsure if this is the best way to pose the question but is there a type of general format that would allow me to query for the latest address stored and return that as a string in the code? Here is what I have written thus far in my load screen view controller, which is designed to place a user based on her/his location.
import UIKit
import CoreLocation
import Parse
class LoadViewController: UIViewController, CLLocationManagerDelegate {
var locationManager = CLLocationManager()
var lastUserLocation = "nil"
var address = ""
var startTime = ""
func findEventProximity(eventObjectId: String){
var eventObjectIdQuery = PFQuery(className: "Events")
eventObjectIdQuery.orderByDescending("createdAt")
eventObjectIdQuery.whereKey("objectId", equalTo: eventObjectId)
eventObjectIdQuery.findObjectsInBackgroundWithBlock({ (objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
print(objects!.count)
if let objects = objects as [PFObject]? {
for object in objects {
object["objectId"] = selectedEventObjectId
/* need to decide wether to store Time in createNewEvent as NSDate or just string
if object["date"] != nil {
self.eventDateLabel.text = object["date"] as! NSDate
}
*/
print("\(object["address"] as! String)")
}
} else {
print(error)
}
}
})
}
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
findEventProximity(selectedEventObjectId)
// Do any additional setup after loading the view.
}
/*MARK: LOCATION*/
/*MARK: LOCATION*/
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
self.locationManager.stopUpdatingLocation()
let userLocation: CLLocation = locations[0]
let userLocationLat = locations[0].coordinate.latitude
let userLocationLong = locations[0].coordinate.longitude
var location = CLLocation(latitude: userLocationLat, longitude: userLocationLong)
print("\(userLocationLat), \(userLocationLong)")
let locationsToCheck: [CLLocation] = [
CLLocation(latitude: lat, long),
CLLocation(latitude: lat, long),
CLLocation(latitude: lat, long),
CLLocation(latitude: lat, long)
]
let locationTags: [String] = [
"P1",
"P2",
"P3",
"P4"
]
var closestInMeters: Double = 50000.0
var closestIndex: Int = -1
for (locationIndex, potentialLocation) in locationsToCheck.enumerate() {
let proximityToLocationInMeters: Double = userLocation.distanceFromLocation(potentialLocation)
if proximityToLocationInMeters < closestInMeters {
closestInMeters = proximityToLocationInMeters
closestIndex = locationIndex
}
}
if (closestIndex == -1) {
self.lastUserLocation = "Other"
// SCLAlertView().showError("No Filter Range", subTitle: "Sorry! We haven’t got a Filter Range for you yet, email us and we’ll get right on it.").setDismissBlock({ () -> Void in
// selectedLocation = self.locationObjectIdArray[0]
// self.performSegueWithIdentifier("eventTableSegue", sender: self)
// })
// self.activityIndicator.stopAnimating()
// UIApplication.sharedApplication().endIgnoringInteractionEvents()
} else if locationTags[closestIndex] != self.lastUserLocation {
self.lastUserLocation = locationTags[closestIndex]
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}

objects not being added to an array - swift iOS

I'm trying to add Task objects to an array called tasks, and then filtered them into another array called filteredTasks by category.
var tasks = [Task]()
var filteredTasks = [Task]()
override func viewDidLoad() {
super.viewDidLoad()
print(categoryPass)
loadTasks()
filterTasks(categoryPass!)
if filteredTasks.isEmpty {
filterTasks(categoryPass!)
}
print(filteredTasks)
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.locationManager.delegate = self
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.delegate = self
tableView.dataSource = self
// Do any additional setup after loading the view, typically from a nib.
}
func loadTasks() {
var taskTitle: String?
var id: String?
var distance: Double?
var category: String?
var locationDict: [String:CLLocation] = [:]
var refHandle = self.ref.child("tasks").observeEventType(FIRDataEventType.Value, withBlock: { (snapshot) in
self.tasksDict = snapshot.value as? NSDictionary
for i in 0 ..< self.tasksDict!.count {
let taskId = self.tasksDict!.allKeys[i] as! String
print ("1")
id = taskId
print (taskId)
let task = self.tasksDict!.objectForKey(taskId) as! NSDictionary
print ("2")
let lat = task.objectForKey("latitude") as! String
let long = task.objectForKey("longitude") as! String
let latNum = Double(lat)! as CLLocationDegrees
let longNum = Double(long)! as CLLocationDegrees
taskTitle = task.objectForKey("title") as? String
category = task.objectForKey("category") as? String
print("3")
// print (taskTitle)
//print (category)
let pointLocation = CLLocation(latitude: latNum, longitude: longNum)
locationDict[taskId] = pointLocation
if (self.locationCurrent == nil) {
self.locationCurrent = self.locationManager.location!
}
print("4")
distance = round(self.locationCurrent!.distanceFromLocation(pointLocation))
var taskAddition = Task(title: taskTitle, id: id, distance: distance, category: category)
print (taskAddition)
self.tasks += [taskAddition]
}
})
}
func filterTasks(searchText: String, scope: String = "All") {
print("filter")
for i in 0 ..< tasks.count {
let taskId = tasksDict!.allKeys[i] as! String
// print (taskId)
let task = tasksDict!.objectForKey(taskId) as! NSDictionary
let taskCategory = task.objectForKey("category") as! String
if (taskCategory.lowercaseString.containsString(searchText.lowercaseString)) {
filteredTasks.append(task[i] as! Task)
}
}
self.tableView.reloadData()
}
The numbers 1,2,3, and 4 all print out in the console, but if I try to print out tasks or filteredTasks, I get [] and a count of 0. Is there a reason why tasks aren't being added to the arrays?
You can't do it that way. The method observeEventType is asynchronous. It takes a closure as a parameter. It returns immediately, before the closure is run.
You need to rewrite your loadTasks function to also take a completion closure. You'd then call it with the code that you want to execute once the tasks array is filled. See my answer to this thread for a working example:
how to return value after the execution of the block? Swift

How do i use mapbox's new MGLOfflinePackDelegate correctly?

I'm creating an app which needs an offline map. I'm testing with MapBox, which supports offline maps since today (yay!). The code I have now seems to work for downloading the map, but the delegate to report on progress never triggers, and I don't have a clue why this is.
I have this class for my mapView:
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()
}
}
}
}
And the MapDownloadController is a View which is displayed as popup (see code above) and has the 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]
}
}
This is all pretty much taken from the documentation, so why are the delegate's functions (func offlinePack) never called? I did test with breakpoints so i am sure it is not. Still, the popup is shown and the region gets downloaded. (Checked with observing network traffic and with other code which lists the offline packs.)
Here’s an extremely simple implementation of Minh’s answer, using the current v3.2.0b1 example code. Expect this answer to become outdated quickly, as we’re still working on the v3.2.0 release.
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.")
}
}
(Cross-posted from this GitHub issue.)
This is 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).

Resources