Observing the region of MKMapView via KVO? - ios

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.

Related

Does a class being its own delegate follow iOS convention?

Sorry this question may sound "subjective" but I think it should have a pretty definitive answer. I have a class "LocationManager" that I want to manage my Core Location logic. I have two options:
LocationManager has a strong property referencing an instance of CLLocationManager. LocationManager is a delegate of CLLocationManager and receives location updates from it as such.
LocationManager is a subclass of CLLocationManager, and says self.delegate = self so that it can receive its own location updates.
I'm curious which of these options is considered the "right" thing to do, I'm sure that there must a be a preferred way. Thanks!
Subclassing CLLocationManager and setting its delegate to self should not be done because it breaks the contract of CLLocationManager. As the class is currently defined, it has a delegate property. This property serves as a contract which states that you may set this property to some other object, and this object will receive delegate notifications. If you subclass CLLocationManager (let's call it MyLocationManager), and if the delegate property of the object points to itself, then you will most likely create a situation where MyLocationManager only works as promised if the user does not use the delegate property for his own purposes. From a users point of view, MyLocationManager is a CLLocationManager without a usable delegate property. This violates Liskovs Substitution Principle, btw. The question to ask here is: would MyLocationManager still work, if some ViewController class decides to use it and have its delegate property point to itself (the ViewController)?
Furthermore, it is no longer "delegation", if you say self.delegate = self. So I would say it is preferrable to use variant 1.
Thanks for the question.
Yes you can do this with no problem. I've a subclass of UITextField which is its own delegate.
The first option seems right to me because it doesn't make a ton of sense to subclass CLLocationManager (#2). What functionality would you be adding to it? If you're not adding anything to it why subclass?
All you care about is encapsulating the messages about location updates. I'd say you're using the delegate/protocol pattern acceptably in the first case.
And Jef is right, there are times where a subclass of another class can be set as its own delegate. Though you need to be careful about how that object responds to certain messages.

how to detect interface changes on iOS application

Is there any possible way to detect every change on User Interface during runtime??
I'm trying to find all objects in the current app interface.
I'm trying to to get all nodes inspecting recursively the main Window, but, for example, how to know if the top viewcontroller changes or if it's added a uiview dynamically, or is presented a modalview??
The main objective is to have a library to do this..
Any idea, help?
Thanks!
You could write your own library based on this, using advanced Objective-C techniques. I do not recommend you to do this, since it mostly breaks MVC patterns on iOS. Depends on what do you want to use it for, maybe analytics?
So these are the options I believe, if you want to actively inspect UIView hierarchy. All options are pretty complicated though.
Swizzle methods such as addSubview and removeFromSuperview of UIView, so you could know when changes like that happens. Including the getters of frame and bounds, if you wish to know the position.
You could use KVO to watch properties such as: subviews, frame, bounds, superview to notice any changes. But at one point you would have to add the same object as the observer (could be singleton).
Decide for an interval that is fired by a NSTimer and go through the hierarchy recursively beginning at keyWindow on UIApplication. This would have a big performance impact though.
There may be other options, but these are the ones I believe to be the best choices.

Calling the delegate methods

Still I couldn't understand completely how these delegate methods are getting called.
I have UIViewController,UITextFieldDelegate in one class which will call its delegate methods without specifying like textField.delegate = self;
But for some different purposes like UIWebViewDelegate we are supposed to enter like webView.delegate = self; and it seems like it is calling its delegate methods. Perfect.
But now I am facing a problem. I am using CLLocationManagerDelegate and also CALayer in same class. For both I am giving location.delegate =self; and layer.delegate =self; At some point both are conflicting each other and only one of the thing is working either CLLocationManagerDelegate or CALayer. The other thing is getting stopped. I don't why it so happens like this? Any reason? How can we overcome this. Even I planned to use some other frameWork, say UIWebView . I will face same problem for those delegate methods also. Can you tell me why it is working in that way ?
The classes that will call its delegates without you specifying them have default implementations, which means that they already know what to do, and will only change their behavior if you override these methods.
Setting 2 or more classes to the same delegate should not interfere with each other (unless for a very weird reason in where the method is named the same in both custom classes).
Your problem is most likely the fact that you havent implemented those methods or are using those classes wrong.
For example, Location manager requires you to create an instance, configure it and START running updates. The most common method for a delegate of this type is the "did update location" (or something like that). Which you have to implement if you want to be informed of every time a new location is received. Otherwise you have to read the location manually whenever you desire.
As a suggestion, every time you set a delegate for an object, you have to do the object.delegate = self; thing. And you probably noticed that you will get a warning until you specify in the header that it conforms to that protocol: for example.
Just control click the UITextFieldDelegate word.
Look for the methods under #required, THOSE you have to always implement. the #optional have default implementations so unless you wanna change the behavior its not needed to implement them.

Monitor MKMapView redraw events

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.

ios superview and subview

I have a superview and I add a subview to make a selection. In the superview (main view) I do the following:
[self.view addSubview:cityViewController.view];
In the cityView, when I have done what I need to do, I just do
self.view removeFromSuperView.
The question is, from within the superview, how can I tell when the subview has removed itself.
There's a few ways, but honestly since the current view controller (let's call it main) is just adding the cityViewController's view, keep the handling of adding/removing the views to the current view controller, and just have the main controller call [cityViewController.view removeFromSuperView]
This way you can execute whatever code you want when it receives this notification (be it a method that fires or a UINotification).
-- edit for sample UINotification code --
main.m
...
//Define cityViewController as an iVar and alloc/init it
[[UINotificationCenter defaultCenter] addObserver:self selector:#selector(didFinishView:) name:#"DidFinishView" object:nil];
[self.view addSubview:cityViewController.view];
...
-(void) didFinishView:(NSNotification *)notification {
[cityViewController.view removeFromSuperView];
}
CityViewController.m
-(IBAction) doneButtonClick:(id) sender {
[[NSNotificationCenter defaultCenter] postNotificationName:#"DidFinishView" object:nil];
}
The quick answer is your view should not be removing itself. It's better practice for a view to communicate user interactions to a relevant controller through an interobject communication mechanism. The most common methods are direct messaging, protocols and notifications. The iOS framework uses all of these and there are great docs explaining them. Here's a brief summary:
Direct messaging. Use this when an object needs to communicate with a specific object of a known type. For example, if MyView is always contained in MyViewController and needs to send messages to it you can add a property to the MyView class that keeps a pointer to the specific MyViewController object. You can then send a message from myView to it's myViewController via [myView.myViewController userDidTapSaveButton] or whatever.
Protocols. A protocol defines a contract between objects that don't know anything about each other other than that they abide by the contract. For example, UITableView knows that it's delegate conforms to the UITableViewDelegate protocol and it can send the required protocol messages to it's delegate. Any object can conform to the UITableViewDelegate protocol.
Notifications. Notifications allows an object to post notifications through a central mechanism (NSNotificationCenter) that other objects can observe and respond to. Notifications are useful when the object posting the notification doesn't know or care what objects are observing it's notifications.
I'd read the relevant docs and other Q&A on SO about these methods. I'd also study up a bit on the MVC (Model/View/Controller) design pattern so you get more comfortable knowing where to put app logic. Generally, a view should only be responsible for it's display (based on properties set by it's controller), observing/responding to user actions, and notifying it's controller for relevant actions.

Resources