Monitor MKMapView redraw events - ios

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.

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.

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.

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.

Related

Internal implementation of UIView's block-based animation methods

Ever since their introduction in iOS 4, I have been wondering about the internal implementation of the UIView's block-based animation methods. In particular I would like to understand what mystical features of Objective C are used there to capture all the relevant layer state changes before and after execution of the animation block.
Observing the black-box implementation, I gather that it needs to capture the before-state of all layer properties modified in the animation block, to create all the relevant CAAnimations. I guess it does not do a snapshot of whole view hierarchy, as that would be horribly inefficient. The animation block is opaque code blob during runtime, so I don't think it can analyze that directly. Does it replace the implementation of property setters on CALayer with some kind of recoding versions? Or is the support for this property change recoding baked-in somewhere deep inside the CALayers?
To generalize the question a little bit, is it possible do create similar block-based API for recording state changes using some Objective C dark magic, or does this rely on knowing and having the access to the internals of the objects being changed in the block?
It is actually a very elegant solution that is built around the fact that the view is the layers delegate and that stand-alone layers implicitly do animate on property changes.
It just happens to be that I gave a BLITZ talk about this at NSConference just a couple of days ago and I posted my slides on GitHub and tried to write down more or less what I said in the presenter notes.
That said: it is a very interesting question that I don't see being asked very often. It may be a bit to broad but I really like curiosity.
UIView animations existed before iOS 4
Ever since their introduction in iOS 4, I have been wondering about the internal implementation of the UIView's block-based animation methods.
UIView animations existed before iOS 4 but in a different style (that is no longer recommended to use because it is more cumbersome to use). For example, animating position and color of a view with a delay could be done like this. Disclaimer: I did not run this code so it may contains bugs.
// Setup
static void *myAnimationContext = &myAnimationContext;
[UIView beginAnimations:#"My Animation ID" context:myAnimationContext];
// Configure
[UIView setAnimationDuration:1.0];
[UIView setAnimationDelay:0.25];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
// Make changes
myView.center = newCenter;
myView.backgroundColor = newColor;
// Commit
[UIView commitAnimations];
The view-layer synergy is very elegant
In particular I would like to understand what mystical features of Objective C are used there to capture all the relevant layer state changes before and after execution of the animation block.
It is actually the other way around. The view is built on top of the layer and they work together very closely. When you set a property on the view it sets the corresponding property on the layer. You can for example see that the view doesn't even have it's own variable for the frame, bounds or position.
Observing the black-box implementation, I gather that it needs to capture the before-state of all layer properties modified in the animation block, to create all the relevant CAAnimations.
It does not need to do that and this is where it all gets very elegant. Whenever a layer property changes, the layer looks for the action (a more general term for an animation) to execute. Since setting most properties on a view actually sets the property on the layer, you are implicitly setting a bunch of layer properties.
The first place that the layer goes looking for an action is that it asks the layer delegate (it is documented behaviour that the view is the layers delegate). This means that when the layer property changes, the layers asks the view to provide an animation object for that each property change. So the view doesn't need to keep track of any state since the layer has the state and the layer asks the view to provide an animation when the properties change.
Actually, that's not entirely true. The view needs to keep track of some state such as: if you are inside of the block or not, what duration to use for the animation, etc.
You could imagine that the API looks something like this.
Note: I don't know what the actual implementation does and this is obviously a huge simplification to prove a point
// static variables since this is a class method
static NSTimeInterval _durationToUseWhenAsked;
static BOOL _isInsideAnimationBlock;
// Oversimplified example implementation of how it _could_ be done
+ (void)animateWithDuration:(NSTimeInterval)duration
animations:(void (^)(void))animations
{
_durationToUseWhenAsked = duration;
_isInsideAnimationBlock = YES;
animations();
_isInsideAnimationBlock = NO;
}
// Running the animations block is going to change a bunch of properties
// which result in the delegate method being called for each property change
- (id<CAAction>)actionForLayer:(CALayer *)layer
forKey:(NSString *)event
{
// Don't animate outside of an animation block
if (!_isInsideAnimationBlock)
return (id)[NSNull null]; // return NSNull to don't animate
// Only animate certain properties
if (![[[self class] arrayOfPropertiesThatSupportAnimations] containsObject:event])
return (id)[NSNull null]; // return NSNull to don't animate
CABasicAnimation *theAnimation = [CABasicAnimation animationWithKeyPath:event];
theAnimation.duration = _durationToUseWhenAsked;
// Get the value that is currently seen on screen
id oldValue = [[layer presentationLayer] valueForKeyPath:event];
theAnimation.fromValue = oldValue;
// Only setting the from value means animating form that value to the model value
return theAnimation;
}
Does it replace the implementation of property setters on CALayer with some kind of recoding versions?
No (see above)
Or is the support for this property change recoding baked-in somewhere deep inside the CALayers?
Yes, sort of (see above)
Creating similar API yourself
To generalize the question a little bit, is it possible do create similar block-based API for recording state changes using some Objective C dark magic, or does this rely on knowing and having the access to the internals of the objects being changed in the block?
You can definitely create a similar block based API if you want to provide your own animations based on property changes. If you look at the techniques I showed in my talk at NSConference for inspecting UIView animations (directly asking the layer for the actionForLayer:forKey: and using layerClass to create a layer that logs all addAnimation:forKey: information) then you should be able to learn enough about how the view is using the layer to create this abstraction.
I'm not sure if recording state changes is you end goal or not. If you only want to do your own animation API then you shouldn't have to. If you really want to do it, You could probably could, but there wouldn't be as much communication infrastructure (delegate methods and callbacks between the view and the layer) available to you as there is for animations.
David's answer is awesome. You should accept it as the definitive answer.
I do have a minor contribution. I created a markdown file in one of my github projects called "Sleuthing UIView Animations." (link) It goes into more detail on how you can watch the CAAnimation objects that the system creates in response to UIView animations. The project is called KeyframeViewAnimations. (link)
It also shows working code that logs the CAAnimations that are created when you submit UIView animations.
And, to give credit where credit is due, it was David who suggested the technique I use.

Passing data to ViewController from a custom view

My single view application on XCode has a custom view that is about half the size of the full iPhone screen. I have implemented a drawing tool that is only useable inside of the custom view. The custom view logs the distance the drawing tool was moved. With only one ViewController, I would like to display the distance variable in a Label outside of the custom fiew frame. Do I need to use protocols and delegates to do this? Or is there a much simpler way? I have been testing with the protocols and delegates method for the past few days and have not gotten anywhere.
First in the interface builder add your custom view as a subview of the main view constrollers view, also add the label. Then add outlets for the label and the view to the view controller (let's call them distanceLabel and drawView respectively).
Now declare distanceDrawn as a property of your custom view and when the tool is moved update it to contain the right number.
Then in the view controllers viewDidLoad you add:
[drawView addObersver:self forKeyPath:#"distanceDrawn" options:NSKeyValueObservingOptionNew context:null]
Also add to the controller:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([object isEqual:drawView]) {
id distance = change[NSKeyValueChangeNewKey]; //change to appropriate type
distanceLabel.text = [NSString stringWithFormat:#"%#", distance];
}
}
Now whenever you change distanceDrawn this method will be called and the dictionary called change will contain the newly set value for the key NSKeyValueChangeNewKey. If you retrieve the value you can update the label.
Your custom view should have a property for the drawing length. You can then read the value from any class that has a reference to the custom view.
Probably you also want to have the view controller update the label every time the drawing length increases. You could handle that with Key-Value Observing. You register for notifications from the custom view object when its drawingLength property changes.
You could do this with a delegate. In some cases I think that's a cleaner solution. It's nice to make clear what sort of updates you can listen for from the custom view. However, this kind of case is just what Key-Value Observing is intended for.

Ideal place to put a method after orientation has changed

I have an issue and here how it goes,
I have a view with a subview, the subview is loaded conditionally, only if the parent view is setHidden property is set to YES;
something like [parentView setHidden:YES] and if([parentView isHidden]),
I want to call a method when the orientation changes and that is the cited snippet above, but I have observed that the method shouldAutorotateToInterfaceOrientation is called 4 times during loading and 2 times during runtime, since the method is called more than once, how can I possibly implement a method call ideally since apple's existing method doesn't seem to give me the intuitiveness to put my custom method call with the existing method.
If I would hack this thing, it is possible, but somebody might have a better idea before resorting to things that in the future would just cause me more trouble than benefit.
TIA
Have you tried with
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration {
// check here for your desired rotation
}

On-demand OpenGL ES rendering using GLKit

I am looking into converting my OpenGL rendering code to take advantage of a few features of GLKit (namely the asynchronous texture loading and the automation provided by GLKView/Controller). However, it appears that the classes are designed mainly to accommodate people rendering using an animation loop, whereas I'm working with on-demand rendering. Additionally, some of the rendering is to a texture rather than the GLKView's framebuffer, so should I be looking to just subclass the GLKView and add additional FBOs?
Is there a recommended approach for this type of setup? I would expect something along the lines of:
Set the view controller's preferredFramesPerSecond to 0, or just
pause the frame updates?
Ignore the glkViewControllerUpdate or glkView:drawInRect: methods
and just draw what I need, when I need it.
Use the view's setNeedsDisplay as with a normal UIView in order
to display the frame (do I need to call bindDrawable given that I
will be rendering to a texture as well?).
Perhaps it's not worth the effort if this is not what the new API is designed for? I wish the documentation was a little more thorough than it is. Perhaps more samples will be provided when the API has 'matured' a little...
Thanks
The approach I ended up using was to not bother with the GLKViewController, but just use GLKView directly under a UIViewController subclass.
Clearly, the GLKViewController is intended for use by people who need a consistent rendering loop for apps such as games. Without it, drawing to the GLKView is as simple as calling [glkView setNeedsDisplay]. Be sure to set enableSetNeedsDisplay to YES in order to enable this behaviour.
If you did still want to make use of a GLKViewController, you can disable the animation rendering loop in viewWillAppear like so:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated]; // setPaused automatically set to NO in super's implementation
[self setPaused:YES];
}
Also, set resumeOnDidBecomeActive to NO to prevent the view controller from resuming again automatically.
Using a plain UIViewController with a GLKView is perfectly acceptable however, and I have seen it recommended by an Apple engineer as an appropriate way to perform on-demand drawing.
I've just converted my code from using an EAGLContext manager I rolled myself to using the GLKit classes.
You suggest you might "..ignore the.. glkView:drawInRect: methods and just draw what [you] need, when I need it". This seems like a sensible option performance-wise; I assume (though haven't tried) if you simply don't specify a GLKViewDelegate or provide a subclassed GLKView with its drawInRect: defined then no animation loop rendering will occur. Have you attempted this?
The alternative would be to simply create some #property (assign, nonatomic) BOOL shouldUpdate; in your MyController : GLKViewController <GLKViewDelegate> class which will only update if there is something to do:
[self setDelegate:self]; // in init or awakeFromNib or other..
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
if ([self shouldUpdate]) { ...
I'm sure you get the idea, it's hardly complicated.
One thing worth mentioning: the official API docs state that viewDidLoad should be used in your GLKViewController for initial GL setup. I had issues with this; for some reason my glCreateShader calls always returned zero. This may have been due to my setting the EAGLContext post-initialisation; I couldn't pass it as an init parameter since I created the controller in Storyboard. However, there was nothing logically wrong with the code, so I offer this friendly warning in case you encounter similar issues. My solution is simply to have the following in my drawInRect:
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
if ([self initialGLSetupDone] == NO) {
[self beforeFirstRender];
[self setInitialGLSetupDone:YES];
}
// .. rest of render code goes here.
}
Obviously it's not ideal to have an IF in there unnecessarily, but it was an easy solution.
Let me know how it goes if you try updating to use GLKit.
After you have created GLKView, add this line:
glkView.enableSetNeedsDisplay = TRUE;
(Because of this, no one will redraw the view automatically)
When you want redraw, insert this line:
[glkView setNeedsDisplay];
... then drawInRect routine will be called only once.
Hope it helps.

Observing the region of MKMapView via KVO?

I have an object that is interested in knowing when the region of a MKMapView is changed. This object is not the delegate of the map view, however. I'm trying the following, where map is a MKMapView:
[map addObserver:self forKeyPath:#"region" options:0 context:nil];
However, observeValueForKeyPath:ofObject:change:context: isn't being called back.
As an interim solution, I have the map's delegate letting this other object know when the map region is changed, but I'd like to uncouple the two objects as they aren't really related.
In Cocoa (Touch), properties of framework objects are only guaranteed to be KVO-compliant if the documentation says so. The docs for -[MKMapView region] make no such claim, so you shouldn't try to use KVO upon it. Even if it happened to work, you'd have no guarantee of complete compliance, or of continued success.
Instead, you'll have to use the delegate method and message other objects from there. Possibly your delegate could broadcast an NSNotification to achieve a similar effect to KVO.

Resources