MKMapView crashes app when view controller popped - iphone

I have a view controller with an MKMapView that calls
[self.mapView setRegion:region animated:YES];
which repositions the map from A to B.
The view controller which holds the MKMapView is set as the delegate and in
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
I have some code that will trigger another setRegion:animated: to the MKMapView so that the map will zoom in on the new position automatically.
Everything works fine if I popViewControllerAnimated: the view controller AFTER the MKMapView animation is done panning and zooming.
However, when I try to popViewControllerAnimated: the current view controller WHILE the MKMapView is running it's animation, the app crashes with "message sent to deallocated instance".
From the looks of the debugger, I think that MKMapView is trying to call a method from a popped and deallocated delegate.
So I tried
[self.mapView setDelegate:nil];
self.mapView = nil;
in viewDidUnload with no luck. The app still crashes consistently.
The only thing I could think of was to create a separate new delegate class and retain that class from the parent view controller so that the MKMapView would have a delegate to call even after the view controller that contains it is deallocated.
Why is this happening?
Are there any other "clean" options?

A friend helped me get this one.
I implemented my own method for popping the view controller instead of using the default navigation controller's back button. I just had to add [self.mapView setDelegate:nil]; before I popped the view controller.
- (void)goBack
{
[self.mapView setDelegate:nil];
[self.navigationController popViewControllerAnimated:YES];
}

OK, this is the real answer. It's from the Apple doc, but it's missing from MKMapView. It's only found under the documentation for its delegate protocol:
"Before releasing an MKMapView object for which you have set a delegate, remember to set that object’s delegate property to nil. One place you can do this is in the dealloc method where you dispose of the map view."
NOTE: This also applies to UIWebView.
I set the MapView's delegate pointer to nil in the delegate's dealloc method, and our crashes seem to have been eliminated.

My problem was not solved by setting delegate of MKMapView to nil in my view Controller
[self.mapView setDelegate:nil];
I had to make a __strong reference of my UIViewController containing MKMapView in my RootViewController.
__strong <#UIViewController#> *vcNewLocation;

I had done Clustering and was marking the Annotation selected like so mapView.selectAnnotation(annotation, animated: true).
while popping, the deinit method used to crash.
So on press of back button I just add this line mapView.deselectAnnotation(selectedAnnotation, animated: false) and it solved the crash.

The following code is likely to asolve your problem:
-(void) viewWillDisappear:(BOOL)animated
{
self.mapView.delegate = nil;
mapView=Nil;
NSLog(#"viewWillDisappear");
}

Related

MKMapView with UserLocation crashes when dismissing view controller

I have a ViewController with a MapView that has UserLocation set and also MKUserTrackingModeFollow set. I have my view controller as the delegate to the mapview.
When I dismiss the Viewcontroller something in the mapview still sends ClientUpdate to my viewcontroller but it crashes as it has now been deallocated.
I tried calling [self.mapView setDelegate:nil] in dealloc but still the same result.
The error is BadAcces when onClientEvent is called on my controller. I am using io5 and ARC.
Any ideas?
I fixed it by putting [self.mapView setDelegate:nil] in viewWillDisappear.

iPhone App - dismissing modal view controller doesn't dealloc it

I have a UIViewController (call it NumberTwo) which I presented as a modal view controller from another UIViewController (call it NumberOne). NumberTwo contains a touchesBegan method which listens for touches, and it also has an accelerometer method which listens for device orientation changes in the x, y, or z direction. NumberTwo has a button called "Done" which, when tapped, dismisses itself as a modal view controller:
[self dismissModalViewControllerAnimated:NO];
But it seems as though it's still listening for touches, and it's still listening for accelerations. How can I completely free up NumberTwo when I dismiss it? I tried adding a release call as follows:
[self dismissModalViewControllerAnimated:NO];
[self release];
but that caused a EXEC_BAD_ACCESS.
Did you release the controller after you presented it? E.g. in your method in NumberOneController that presents it, do you have something like:
NumberTwoController * controller = [NumberTwoController alloc] init];
// do stuff to config controller
[self presentModalViewController: controller];
[controller release];
Unless you want to hang on to NumberTwoController for re-use, this would be the usual pattern. The presentModalViewController method ensures that the controller is retained while it's in use. It should then get tidied up when, within NumberTwoController, you call [self dismissModalViewControllerAnimated: NO].
I had a very similar issue that plagued me for days. It turned out that my view controller class wasn't being deallocated when I dismissed it because that view controller had an active NSTimer that wasn't being invalidated (stopped). I was able to kill the timer in viewDidDisappear.
Make sure you are releasing everything you use when you finish with it; The dealloc method is only called when the UIViewController and all of its properties/objects are no longer in use. Never use [self release]; you need to release it from the view controller that created it after you are finished with it.

Releasing UIViewController when not in use

Hi I hope somebody can help me with this problem.
I have a UIViewController named "Login" (for example) and when the user has successfully logged in this will call another controller to replace the "Login" controller like below:
[self presentModalViewController:anotherController animated:YES].
I do not need the previous controller any more so I placed a [self release] as shown in the code snippet below.
LoginController.m
- (void)viewDidDisappear:(BOOL)animated {
[self release];
}
This will then call the LoginController's dealloc method and I can be sure it's released.
Now in the new controller that is now in view has a button which calls a UINavigationController like below:
[self presentModalViewController:settingsNavigationController animated:YES];
But this crashes the app which would normally work if I didn't release the previous LoginController.
There is probably an easier or more logical method to release the controller but as I am running out of ideas I sometimes use drastic measures.
Thank you.
You should not be releasing the LoginController, at least not in its own -viewDidDisappear:. That controller is still in use and can be referenced, for example by the navigation controller's parentViewController property. Release the controller when it is no longer part of your view controller hierarchy, not just when it is no longer visible.
In addition [self release] is a warning sign that you are applying incorrect memory management.
1) Replace the "Login" controller
presentModalViewController doesn't replace your login view controller, but it puts anotherController on top of your login view controller.
2) viewDidDisappear
You should read documentation. Quote - You can override this method to perform additional tasks associated with dismissing or hiding the view. If you override this method, you must call super at some point in your implementation.
In other words, you must call [super viewDidDisappear:animated] too.
3) Memory Management
You should definitely need to read Memory Management Guide - http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/MemoryMgmt/MemoryMgmt.html
4) View Controller
You should definitely need to read View Controller Programming Guide too - http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/Introduction/Introduction.html
Two problems with what you are doing.
First, viewDidDisappear is NOT a destructor. Do not release self there. viewDidDisAppear and appear are used for visibility of the view, not in/out of memory.
Second, presentModalViewController is intended for presenting a MODAL view controller, ie child.
You should consider setting up a UINavigationController and calling
[navigationController popToRootViewControllerAnimated:FALSE];
[navigationController pushViewController:(UIViewController*)controller animated:TRUE];
also in some situations you can get away with an [autorelease] view controller using present modal.

Accessing a MKMapView through the tab bar

I have a tabbar application and on the first tab I have a MKMapView. What I want to do is from somewhere else in the application, switch the active tab to the mapview and set the mapview's region based on the data in the previous view (the one with the button to switch to the mapview).
What I've tried is:
[self.tabBarController setSelectedView:0];
UIMapViewController *mapView = [self.tabBarController.viewControllers objectAtIndex:0];
[mapView displayBookmarkAnnotation:bookmark];
This just causes the app to crash unable to find the method I created.
I don't think I've chosen the best path to implement this but I'm really not sure how I should go about it.
[Update]
Casting the controller returned by the tabBarController had no effect.
[Solved]
I was trying to cast a UINavigationController to my mapView
[self.tabBarController setSelectedView:0];
UINavigationController *navController = [self.tabBarController.viewControllers objectAtIndex:0];
//if the tab has other views open, return to mapView
[navController popToRootViewControllerAnimated:YES];
UIMapViewController *mapView = (UIMapViewController *)[navController visibleViewController];
[mapView customMessage:object];
Are you sure the main view controller for that tab is not a UINavigationController? If so, you can get the root view controller for that which should be your UIMapViewController.
It would be good to put a direct reference in the AppDelegate though if you are going to be calling it from elsewhere.
Why not route it through your AppDelegate? The AppDelegate can have a UITabBarController and the MKMapView (both wired through interface builder.) The UIButton handler would then also be in the AppDelegate so that it can call -[UITabBarController setSelectedView:] and -[MKMapView setRegion:].
What you want to do is create a subclass or a category of the UITabBarController that
registers for NotificationCenter events that you define
handles the events with a new selector. I generally use do/did naming convention for them.
When the event comes through you set the selectedIndex.

why does viewDidAppear not get triggered?

i have a root view controller that inserts a subview at index 0 at its viewDidLoad method.
i am trying to get the subview to become firstResponder, but can only do this - from my understanding - in the subview's viewDidAppear method.
here's the line of code i added to the root view controller's viewDidLoad method:
[self.view insertSubview: subViewController.view atIndex: 0];
the subviewcontroller has a xib, subViewController.xib, that is shown correctly at runtime. nevertheless, the subViewController's viewDidAppear does not get triggered.
any idea why this happens? any idea how to remedy this - apart from calling viewDidAppear manually (doing so results in failure to become firstResponder)?
thanks,
mbotta
You have to push the view controller on to a navigation stack in order for it's delegate methods to get called. Adding your view controller's view to the subview array won't call them. The first thing you should do is read the View Controller Programming Guide from Apple as this will save you from some headaches you're creating by doing this in a non-standard way.
Instead of adding the view to your root view controller subviews, do this:
SubviewController *controller = [[SubviewController alloc] init];
[[self navigationController] pushViewController:controller animated:YES];
[controller release], controller = nil;
Now your delegate methods will get called. If you don't have a navigation controller as your root view controller, though, this won't work.
If I recall correctly (sorry can't find the place in docs now) -viewDidAppear does not get called for subviews. You must call it manually in the -viewDidAppear method of parent view controller.