Unfortunately MapKit itself provides no solution to track rotation changes. It provides events only in the beginning and at the end of rotation. And even more: it does not update heading value for camera, while rotating the map.
I had the same necessity and created own solution in Swift, which worked for me.
1. Subclass MKMapView to process its data
The most simple part:
class MyMap : MKMapView {
}
2. Find the object, which reliably has actual map rotation value
MKMapView
is a kind of UIView container, which contains a kind of canvas inside, where the map is rendered and then transformed. I have researched MKMapView
during runtime, exploring its subviews. The canvas has class name MKScrollContainerView
.
You have to control the instance, so you:
- add the object to class
- write the function to find that object inside
MKMapView
- find the canvas and save it
The code:
class MyMap : MKMapView {
var mapContainerView : UIView?
init() {
...
self.mapContainerView = self.findViewOfType("MKScrollContainerView", inView: self)
...
}
func findViewOfType(type: String, inView view: UIView) -> UIView? {
if view.subviews.count > 0 {
for v in view.subviews {
if v.dynamicType.description() == type {
return v
}
if let inSubviews = self.findViewOfType(type, inView: v) {
return inSubviews
}
}
return nil
} else {
return nil
}
}
}
3. Calculate rotation
MKScrollContainerView
is rotated simply by changing its transform
property. Rotation matrix, used for that purpose, is described in Apple's documentation:
https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/CGAffineTransform/#//apple_ref/c/func/CGAffineTransformMakeRotation
It looks this way:
cosA sinA 0
-sinA cosA 0
0 0 1
The function, to calculate rotation, based on that matrix, looks this way:
class MyMap : MKMapView {
...
func getRotation() -> Double? {
if self.mapContainerView != nil {
var rotation = fabs(180 * asin(Double(self.mapContainerView!.transform.b)) / M_PI)
if self.mapContainerView!.transform.b <= 0 {
if self.mapContainerView!.transform.a >= 0 {
} else {
rotation = 180 - rotation
}
} else {
if self.mapContainerView!.transform.a <= 0 {
rotation = rotation + 180
} else {
rotation = 360 - rotation
}
}
return rotation
} else {
return nil
}
}
...
}
4. Track rotation constantly
The only way I found to do this is to have infinite loop, which checks rotation value every loop call.
To implement that you need:
- MyMap listener
- function, to check rotation
- timer, to call function every X seconds
Here is my implementation:
@objc protocol MyMapListener {
optional func onRotationChanged(rotation rotation: Double)
}
class MyMap : MKMapView {
...
var changesTimer : NSTimer?
var listener : MyMapListener?
var rotation : Double = 0
...
func trackChanges() {
if let rotation = self.getRotation() {
if rotation != self.rotation {
self.rotation = rotation
self.listener?.onRotationChanged(rotation: rotation)
}
}
}
func startTrackingChanges() {
if self.changesTimer == nil {
self.changesTimer = NSTimer(timeInterval: 0.1, target: self, selector: #selector(MyMap.trackChanges), userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(self.changesTimer!, forMode: NSRunLoopCommonModes)
}
}
func stopTrackingChanges() {
if self.changesTimer != nil {
self.changesTimer!.invalidate()
self.changesTimer = nil
}
}
}
That's all ;)
You can download sample project in my repo: https://github.com/d-babych/mapkit-wrap