1
votes

When the state is restored for a mapView, it's slightly zoomed out from where it was during the save. I created an iOS Single View App containing only a MKMapView filling the whole view to try and troubleshoot this. My debug print statements show that the map values are being saved and restored properly but the span latitude or longitude eventually change. What am I missing?

EDIT:

This question is the same topic as:
MKMapView setRegion "snaps" to predefined zoom levels?
MKMapView show incorrectly saved region

As an experiment, I added a class variable of type MKCoordinateRegion and assigned the region read in the decodeRestorableState method to it. Also I added a button and action handler to the view controller which set the mapView's region to the value of the added variable. When tapping the button, the map zoomed into the position that was expected from the restoration. It's odd to me that the location coordinates work incorrectly in the decodeRestorableState method but those same coordinates work correctly in the button's action handler.

import UIKit
import MapKit

class ViewController: UIViewController {
    @IBOutlet weak var mapView: MKMapView!
}

extension MKMapView {
    override open func encodeRestorableState(with coder: NSCoder) {
        super.encodeRestorableState(with: coder)
        region.encodeRestorableState(with: coder)
        camera.encodeRestorableState(with: coder)
    }

    override open func decodeRestorableState(with coder: NSCoder) {
        super.decodeRestorableState(with: coder)
        region.decodeRestorableState(with: coder)
        camera.decodeRestorableState(with: coder)
    }
}

extension MKCoordinateRegion {
    private static let KEY_LATITUDE  = "Region.latitude"
    private static let KEY_LONGITUDE = "Region.longitude"
    private static let KEY_WIDTH     = "Region.spanWidth"
    private static let KEY_HEIGHT    = "Region.spanHeight"

    func encodeRestorableState(with coder: NSCoder) {
        coder.encode(center.latitude, forKey: MKCoordinateRegion.KEY_LATITUDE)
        coder.encode(center.longitude, forKey: MKCoordinateRegion.KEY_LONGITUDE)
        coder.encode(span.latitudeDelta, forKey: MKCoordinateRegion.KEY_HEIGHT)
        coder.encode(span.longitudeDelta, forKey: MKCoordinateRegion.KEY_WIDTH)
    }

    mutating func decodeRestorableState(with coder: NSCoder) {
        center.latitude = coder.decodeDouble(forKey: MKCoordinateRegion.KEY_LATITUDE)
        center.longitude = coder.decodeDouble(forKey: MKCoordinateRegion.KEY_LONGITUDE)
        span.latitudeDelta = coder.decodeDouble(forKey: MKCoordinateRegion.KEY_HEIGHT)
        span.longitudeDelta = coder.decodeDouble(forKey: MKCoordinateRegion.KEY_WIDTH)
    }
}

extension MKMapCamera {
    private static let KEY_ALTITUDE  = "Camera.altitude"
    private static let KEY_HEADING   = "Camera.heading"
    private static let KEY_PITCH     = "Camera.pitch"
    private static let KEY_LATITUDE  = "Camera.latitude"
    private static let KEY_LONGITUDE = "Camera.longitude"

    func encodeRestorableState(with coder: NSCoder) {
        coder.encode(altitude, forKey: MKMapCamera.KEY_ALTITUDE)
        coder.encode(heading, forKey: MKMapCamera.KEY_HEADING)
        coder.encode(Double(pitch), forKey: MKMapCamera.KEY_PITCH)
        coder.encode(centerCoordinate.latitude, forKey: MKMapCamera.KEY_LATITUDE)
        coder.encode(centerCoordinate.longitude, forKey: MKMapCamera.KEY_LONGITUDE)
    }

    func decodeRestorableState(with coder: NSCoder) {
        altitude = coder.decodeDouble(forKey: MKMapCamera.KEY_ALTITUDE)
        heading = coder.decodeDouble(forKey: MKMapCamera.KEY_HEADING)
        pitch = CGFloat(coder.decodeDouble(forKey: MKMapCamera.KEY_PITCH))
        centerCoordinate.latitude = coder.decodeDouble(forKey: MKMapCamera.KEY_LATITUDE)
        centerCoordinate.longitude = coder.decodeDouble(forKey: MKMapCamera.KEY_LONGITUDE)
    }
}
1

1 Answers

1
votes

Had the same issue. Workaround for me was to set mapView.camera in viewDidAppear of my view controller.

let mapCameraKey = "MapCameraKey"
var restoredCamera: MKMapCamera?

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    if let restoredCamera = restoredCamera {
        mapView.camera = restoredCamera
    }
}

override func encodeRestorableState(with coder: NSCoder) {
    super.encodeRestorableState(with: coder)

    coder.encode(mapView.camera, forKey: mapCameraKey)
}

override func decodeRestorableState(with coder: NSCoder) {
    super.decodeRestorableState(with: coder)

    restoredCamera = coder.decodeObject(forKey: mapCameraKey) as? MKMapCamera
}