Crash while getting my location on GoogleMaps on iPhone - ios

I am writing you because I am stuck with a super strange error.
I tried to add GMS on my app, and finally, when I have to get device location, I am stuck with this crash:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x7ff7fd82def0 of class GMSMapView was deallocated while key value observers were still registered with it. Current observation info: (
Context: 0x0, Property: 0x7ff7fdc2f470>
Code is initialized here:
self.mapView.myLocationEnabled = YES;
self.mapView.mapType = kGMSTypeNormal;
self.mapView.settings.compassButton = YES;
self.mapView.settings.myLocationButton = YES;
//=> Listen to the myLocation property of GMSMapView.
[self.mapView addObserver:self
forKeyPath:#"myLocation"
options:NSKeyValueObservingOptionNew
context:NULL];
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([keyPath isEqualToString:#"myLocation"] && [object isKindOfClass:[GMSMapView class]])
{
appDelegate().curUserLocation = [change objectForKey:NSKeyValueChangeNewKey];
[self.mapView animateToCameraPosition:[GMSCameraPosition cameraWithLatitude:self.mapView.myLocation.coordinate.latitude
longitude:self.mapView.myLocation.coordinate.longitude
zoom:15]];
}
}
and still no success. I put breakpoint in observerValueForKey method, and once is out from here, crash appears. No way to get the idea.
I also removed observer :
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.mapView removeObserver:self forKeyPath:#"myLocation"];
//=> Set map delegate to nil (to avoid: mapView:regionDidChangeAnimated:]: message sent to deallocated instance )
self.mapView.delegate = nil;
self.mapView = nil;
}
and no success.
Can anyone help me with this ? I tried all possible solutions, but no way.
Thanks in advance!

How already said in comment.. the GMSMapView must be Strong
Your code will not work in iOS 8 if you not call requestWhenInUseAuthorization
I recommend you to use only the CLLocationManager instead KVO... but is your decision.
Attached my example - I modified the Google example... with 2 example:
with KVO
only with CLLocationManager
If you have any issues... just let me know
PS Don't forget to add you API Key
Happy coding!
UPDATE
if you startUpdatingLocation.. don't forget to stop it somewhere with stopUpdatingLocation.. let say in ViewDidDisapear

If you register inside viewWillAppear:, use viewWillDisappear to remove observer.
If you register inside viewDidLoad, use deinit to unregister the observer. Always use the counter part to register and unregister and that should be fine
Swift 3
deinit {
mapView.removeObserver(self, forKeyPath: "myLocation")
}

Related

KVO check for change of clipsToBounds of all subviews in an UIView in objective c

I am trying to implement a KVO example for clipsToBounds property of all subviews in my UIView. I do not quite understand how to change the value in observeValueForKeyPath method. I am using this code:
-(void)ViewDidLoad{
[self.navigationController.view addObserver:self forKeyPath:#"clipsToBounds" options:NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld context:nil];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(#"Triggered...")
}
It is triggered when ever i change the property clipToBounds of a subview that exists in the UIView i have. I need to change the value back to false for every trigger that happens. What should i write inside the observeValueForKeyPath to change the clipsToBounds property? Any help appreciated.
of course adding the Observer must be done before it works.
Guessing your typo in "ViewDidLoad" would just never be called because it should be "viewDidLoad".
Apart from that your KVO pattern could look like..
static void *kvoHelperClipsToBounds = &kvoHelperClipsToBounds;
-(void)viewDidLoad {
[self.navigationController.view addObserver:self forKeyPath:#"clipsToBounds" options:NSKeyValueObservingOptionNew context:&kvoHelperClipsToBounds];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == kvoHelperClipsToBounds) {
NSLog(#"context compared successful...");
//be careful what you cast to.. i dont check isKindOf here.
UINavigationBar* navbar = (UINavigationBar*)object;
if (navbar.subviews.count > 1) {
__kindof UIView *sub = navbar.subviews[1];
if (sub.clipsToBounds) {
dispatch_async(dispatch_get_main_queue(),^{
sub.clipsToBounds = NO;
[self.navigationItem.titleView layoutIfNeeded];
});
}
}
}
// or compare against the keyPath
else if ([keyPath isEqualToString:#"clipsToBounds"]) {
NSLog(#"classic Key compare Triggered...");
}
else
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
[super observeValueForKeyPath...] passes not recognized keyPath to super to let super's class KVO work, otherwise those would be ignored if super's implementation would rely on observing them. Which should also explain how you could observe all subviews if needed. But think about that there could be potentially hundrets of subviews triggering observeValueForKeyPath if a subclass of UIView would implement it and all subviews (or the ones you like) would be inherited also from this special subclass.
When you change clipsToBounds inside the KVO where you observe it, you possibly invoke a loop, specially when you watch both - old and new values. you would change the property, the property triggers kvo, kvo changes the property, the property triggers kvo and on and on.
set [self.navigationController.view setClipsToBounds:YES] to change the property. But if done inside KVO it will trigger KVO again as explained.
Usually you would set clipsToBounds in -initWithFrame: or in -initWithCoder: or via Interface Builder and maybe just observe if it gets changed to adapt some other code.
Sidenote: the context just needs to be unique to distinguish it from other KVO.. it could also be reference to a real objects pointer.
Don't forget added Observers must be removed before deallocation.

Xcode 8.2 app Crash -[viewcontroller .cxx_destruct] symbolicated crash report

App is experiencing following crash and unable to understand the cause behind of this crash. This crash report I got it from App Store. This is the crash report screenshot
It is mostly affecting on iOS 10.2. In this class I'm using Google Maps, Pageviewcontroller and Timer. So, anyone can tell me how to figure out it?
This crash is happening due to fetching user current location from Google Maps by using addObserver forKeyPath:#"myLocation" options:NSKeyValueObservingOptionNew.
While dealloc Google Maps, that time you need to remove this Observer. Otherwise app will crash with following error
NSInternalInconsistencyException: An instance 0x1759f350 of class GMSMapView was deallocated while key value observers were still registered with it. Current observation info: ( Context: 0x0, Property: 0x177a4490> )
you need to addObserver before adding Google Maps to mapView like following:
// Listen to the myLocation property of GMSMapView.
[mapView_ addObserver:self
forKeyPath:#"myLocation"
options:NSKeyValueObservingOptionNew
context:NULL];
self.view = mapView_;
// Ask for My Location data after the map has already been added to the UI.
dispatch_async(dispatch_get_main_queue(), ^{
mapView_.myLocationEnabled = YES;
});
#pragma mark - KVO updates
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if (!firstLocationUpdate_) {
// If the first location update has not yet been received, then jump to that
// location.
firstLocationUpdate_ = YES;
CLLocation *location = [change objectForKey:NSKeyValueChangeNewKey];
mapView_.camera = [GMSCameraPosition cameraWithTarget:location.coordinate
zoom:14];
}
}
then add this code also for removing the observer
- (void)dealloc {
[mapView_ removeObserver:self
forKeyPath:#"myLocation"
context:NULL];
}
for more details: Google Maps iOS SDK, Getting Current Location of user

iOS: Update mutable array and use KVO to see chaneg

I've looked at some other SO answers in regards to this and I thought I was implementing my code correctly but I am not getting results.
I have a mutable array property - arrLocations. In my .m file, in viewDidLoad I set up an observer for it and then add an item:
self.arrLocations = [[NSMutableArray alloc] init];
//add an observer to know when geocoding loops are updated
[self addObserver:self forKeyPath:#"arrLocations" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
[self insertObject:#"test" inArrLocationsAtIndex:0];
and then I have the KVO method:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if([keyPath isEqualToString:#"arrLocations"]) {
NSLog(#"Showing contents of self.arrLocations\n%#", self.arrLocations);
}
}
But the observer method never gets called.
The observer never gets called because the pointer to your array stays the same when you change the contents of your array.
You would have to add an observer to the array itself and observe a key of the array. Something like count. But you cannot do that because NSMutableArray is not KVO compliant.
So, to make this work you have to find another way. My first idea would be to create a wrapper class for NSMutableArray that fires a notification each time you add or remove items to your array.

was deallocated while key value observers were still registered with it

Im having issues with this KVO-coding. In the code below I was first scanning for changes in key currentLocation, which was working fine but yet was calling getData twice, because, I presume, CLLocation has both altitude and longitude and hence changes twice. Okay, I thought I could neatly just scan either of those:
-(void)viewWillAppear:(BOOL)animated{
dataManager = [[DataManager alloc]init];
[dataManager addObserver:self forKeyPath:#"currentLocation.altitudeā€
options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context{
//if(context==locationContext)
location = [object valueForKeyPath:keyPath];
//If scanning just currentLocation will get 2 events send....
if([keyPath isEqualToString:#"currentLocation.altitude"]){
if(location != nil){
//connect and get data (if location changed more than WILL BE SPECIFIED
[dataManager getData];
}
}
}
But now I start getting following error msg, which I didn't see before:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x1742050d0 of class CLLocation was deallocated while key value observers were still registered with it.
When an observer is deallocated, the NSNotificationManager doesn't figure it out, and when it wants to send a message, it sends it to the deallocated observer - and then things go wrong.
You typically add code to your dealloc that unregisters your object from the notification manager.

After unregistering, I get "class deallocated while key value observers were still registered with it"

I have some code which applies to a number of objects, registering my class as the KVO:
for (SPPanelManager *manager in self.panelManagers) {
[manager addObserver:self forKeyPath:#"dataFetchComplete" options:0 context:NULL];
[manager fetchData];
}
Then when it observes a change, which happens on every of these objects, I un-register:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"dataFetchComplete"] && ((SPPanelManager *)object).dataFetchComplete) {
[object removeObserver:self forKeyPath:#"dataFetchComplete"];
//Other stuff
}
}
Then when I leave the UIViewController later, I get these errors for each of the manager objects:
An instance of class was deallocated while key value observers were
still registered with it. Observation info was leaked, and may even
become mistakenly attached to some other object.
I'm not sure why it's giving me this error - these are the only 2 places that KVO is ever referenced so it's not another observer.
Your class(observer) is being deallocated during some activity. You must unregister it before it is deallocated or not is in further use. Use code below in viewDidUnload: or dealloc:
for (SPPanelManager *manager in self.panelManagers) {
[manager removeObserver:self forKeyPath:#"dataFetchComplete" context:NULL];
}
Don't try to add or remove observers in observeValueForKeyPath:ofObject:change:context:. KVO expects the list of observers for a given Tuple(object, keyPath, context) to remain the same across a notification for that combination. Even it it works "sometimes" the behavior is non-deterministic because the order in which observers are notified is not guaranteed (it probably uses a set-type data structure internally.)
The simplest way around this problem might look something like:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"dataFetchComplete"] && ((SPPanelManager *)object).dataFetchComplete) {
CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopCommonModes, ^{
[object removeObserver:self forKeyPath:#"dataFetchComplete"];
});
}
}
This will cause the observation to be removed at the next possible point in the run loop (barring custom run loop modes, which might delay its execution a bit, but usually won't be a problem). Also, you shouldn't need to worry about either self or object getting deallocated because they will be retained by the block closure until the block has executed and is, itself, releases.
As you've discovered, KVO isn't a particularly great API for one-shot notifications.
As to the initial error message, you'll need to remove the observation. You can probably get away with doing this in the observing object's dealloc but you should really avoid doing "real work" in dealloc and removing observations is arguably "real work." Unfortunately, there's not a standard teardown pattern in Cocoa, so you'd have to trigger the teardown yourself, perhaps when you segue out of the view controller, or something like that. Another answer suggested viewDidUnload but that is deprecated in iOS 6 and will never be called, so it's not a good approach any more.
You've to simply dealloc your GMS_MapView Object and as well as remove the MapView Observer forkeypath.
(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[objGMS_MapView removeObserver:self forKeyPath:#"myLocation"];
//=> Set map delegate to nil (to avoid: mapView:regionDidChangeAnimated:]: message sent to deallocated instance )
objGMS_MapView.delegate = nil;
objGMS_MapView = nil;
}

Resources