Monitor MKMapView redraw events

2019-03-31 07:15发布

I have some UI that I need to redraw based on changes to an MKMapView when the user pans or zooms the map.

Currently I am using a move event gesture recogniser and MKMapViewDelegate regionDidChangeAnimated messages to redraw my dependant UI. This is 90% of what I need.

The events I am missing are from the point the user lifts their finger (no more move events) to when the MKMapViewDelegate regionDidChangeAnimated message is fired. During that period the map slowly pans to a halt and my dependant UI is stuck with map tile content that is out of synch.

Is there a lower level API that will notify me when UIView (in this case MKMapView) content is redrawn?

Update

I tried creating a proxy MKMapView subclass that forwarded drawRect calls onto my supplied delegate. I get the first draw event but none of the subsequent ones, so this doesn't help with my predicament.

3条回答
孤傲高冷的网名
2楼-- · 2019-03-31 08:03

IIRC, MKMapView is unfortunately not KVO-compliant for @"region".

You might hack you way setting up an NSTimer in regionWillChangeAnimated, using it to refresh you UI during the scroll/pan/etc, and discarding it in regionDidChangeAnimated. Not quite elegant though, and it may not suit your needs if you need to be really fast.

Alternatively, you may look for the UIScrollView in MKMapView's subviews, and observe its transform.

(I haven't tested any of these.)

In any case, I doubt that monitoring redraw events on a MKMapView will be of any use : MKMapView uses CATiledLayer to perform its drawing asynchronously, so you can't even be sure when it's done.


Edit : This apparently does not work with iOS 6. Of course, this should not really come as a surprise. However, as far as I know, the delegate methods behave the same, so the OP's problem is still real. I haven't looked for an updated solution.

查看更多
何必那么认真
3楼-- · 2019-03-31 08:10

MKMapView has many subviews that redraws. It is very hard to find which view or layer drawed...

Alternatively you could try to find some of MKMapView properties are changed. You can do this with Key Value Observing (KVO) mechanics. http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html

Eg. (properties can be changed to whatever you need)

  [myMapView addObserver:self forKeyPath:@"region" options:NSKeyValueObservingOptionNew context:nil];
  [myMapView addObserver:self forKeyPath:@"centerCoordinate" options:NSKeyValueObservingOptionNew context:nil];

And you should implement observeValueForKeyPath:ofObject:change:context:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    // Do something that you want with keyPath;
}

Whenever your mapView has new values for each property you defined, this method will be fired.

查看更多
Fickle 薄情
4楼-- · 2019-03-31 08:17

Hate to post THESE kind of solutions, but. MKMapView has many subview in itself. In it's subviews hierarchy there's an view with class MKTiledView, which have TiledLayer as layer.

So, actually, you can't resolve notifications of rendering in "normal" way.

Tiled layer renders it's contents by constantly calling -drawLayer:inContext: method of it's delegate, which MKTiledView is. Those calls can be performed simultaneosly in different threads.

You're not receiving norifications(updates) from MKMapView because it isn't updating itself. Only underlying contents of it are updating.

So. There's always better solution exists. My solution depends on view hierarchy and method's swizzling. It's up to you, to use it or not.

Creating category-method in which we will post "update notifications" to custom view that need to be updated

@implementation UIView (Custom)

- (void)drawCustomLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
   NSLog(@"Need to draw custom layer :%@ in context %@, Thread: %@", layer, ctx, [NSThread currentThread]);

   // Calling old method
   [self drawCustomLayer:layer inContext:ctx];
}

@end

// Exchanging method implementation of MKTiledView to our custom implementation

#import <objc/runtime.h>

Class tileViewclass = NSClassFromString(@"MKMapTileView");
Class viewClass = NSClassFromString(@"UIView");
SEL originalSelector = @selector(drawLayer:inContext:);
SEL newSelector = @selector(drawCustomLayer:inContext:);
Method origMethod = class_getInstanceMethod(tileViewclass, originalSelector);
Method newMethod = class_getInstanceMethod(viewClass, newSelector);
method_exchangeImplementations(origMethod, newMethod);

Still looking for better solution.

查看更多
登录 后发表回答