0
votes

Whenever my map view region changes, I'm saving the center and span values to NSUserDefaults so when the app is opened again it will be in the same place.

func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    let defaults = NSUserDefaults.standardUserDefaults()
    defaults.setValue(myMapView.centerCoordinate.latitude, forKey: "lat")
    defaults.setValue(myMapView.centerCoordinate.longitude, forKey: "long")
    defaults.setValue(myMapView.region.span.latitudeDelta, forKey: "latDelta")
    defaults.setValue(myMapView.region.span.longitudeDelta, forKey: "longDelta")
}

Here's where it is set from the defaults in my viewDidLoad:

override func viewDidLoad() {
    super.viewDidLoad()

    let defaults = NSUserDefaults.standardUserDefaults()
    if  let lat = defaults.valueForKey("lat"),
        let long = defaults.valueForKey("long"),
        let latDelta = defaults.valueForKey("latDelta"),
        let longDelta = defaults.valueForKey("longDelta")

    {
        let center: CLLocationCoordinate2D = CLLocationCoordinate2DMake(lat as! Double, long as! Double)
        let span: MKCoordinateSpan = MKCoordinateSpanMake(latDelta as! Double, longDelta as! Double)
        let region: MKCoordinateRegion = MKCoordinateRegionMake(center, span)
        myMapView.setRegion(region, animated: true)
    }
}

However, when I open the app again, the region isn't shown as it was when the app was closed. The values that aren't saved correctly seem to be incremented by a constant value each time I close and reopen. What's going on?

I switched to using a dictionaryForKey approach, and here's the resulting dictionary upon 3 successive loads, without changing the map region at all:

["long": -95.78, "latDelta": 76.06, "lat": 37.13, "longDelta": 61.27]
["long": -95.78, "latDelta": 88.18, "lat": 31.93, "longDelta": 76.82]
["long": -95.78, "latDelta": 100.69, "lat": 25.71, "longDelta": 86.56]

The long value is saved properly, but the other 3 are behaving strangely.

4

4 Answers

1
votes

Another Swift option (in Swift 5), abstracting the code into an extension on the region struct.

Here's an extension on the region struct that allows you to write it from multiple places:

extension MKCoordinateRegion {
  public func write(toDefaults defaults:UserDefaults, withKey key:String) {
    let locationData = [center.latitude, center.longitude,
                        span.latitudeDelta, span.longitudeDelta]
    defaults.set(locationData, forKey: key)
  }

  public static func load(fromDefaults defaults:UserDefaults, withKey key:String) -> MKCoordinateRegion? {
    guard let region = defaults.object(forKey: key) as? [Double] else { return nil }
    let center = CLLocationCoordinate2D(latitude: region[0], longitude: region[1])
    let span = MKCoordinateSpan(latitudeDelta: region[2], longitudeDelta: region[3])
    return MKCoordinateRegion(center: center, span: span)
  }
}

Using the above extension requires only the following code in a view controller that contains the map view:

private let kMapRegion = "region"

override func viewWillAppear(_ animated: Bool) {
  if let region = MKCoordinateRegion.load(fromDefaults: UserDefaults.standard, withKey: kMapRegion) {
    mapView.region = region
  }
}

override func viewWillDisappear(_ animated: Bool) {
  mapView.region.write(toDefaults: UserDefaults.standard, withKey: kMapRegion)
}

(UserDefaults after iOS 12 no longer needs synchronize)

0
votes

You can save NSDictionary Data directly like as following.

let locationData = ["lat":0.22222, "lng":0.33333, "D_lat":0.0001, "D_lng":0.0001] NSUserDefaults.standardUserDefaults().setObject(locationData, forKey: "location") NSUserDefaults.standardUserDefaults().synchronize()

And Otherwise you can get NSDicationary Data like as following.

let savedLocation = NSUserDefaults.standardUserDefaults().dictionaryForKey("location")

or

let savedLocation = NSUserDefaults.standardUserDefaults().dataForKey("location") as? NSDictionary

0
votes

You'd better use

NSUserDefaults.standardUserDefaults().setDouble(locationData, forKey: "locationData") & NSUserDefaults.standardUserDefaults().synchronize()

when you save location data.

But user this

NSUserDefaults.standardUserDefaults().doubleForKey("locationData")

when you get location data.

0
votes

I realized that the problem comes when saving the region center and span in the regionDidChangeAnimated function. This leads to unpredictable results. A better place to save is in the viewWillDisappear function:

override func viewWillDisappear(animated: Bool) {
    let defaults = NSUserDefaults.standardUserDefaults()
    let locationData = ["lat":myMapView.centerCoordinate.latitude
        , "long":myMapView.centerCoordinate.longitude
        , "latDelta":myMapView.region.span.latitudeDelta
        , "longDelta":myMapView.region.span.longitudeDelta]
    defaults.setObject(locationData, forKey: "location")
}