(Note: I filed this question before in the context of my project, but I've now recreated the crash in a test project. Any help in telling me what I'm doing wrong would be appreciated.)
The crash occurs when calling ABPeoplePicker from another modal viewcontroller. Specifically, the main window has a NavController, which loads myVC. myVC then loads a modal NavController containing my controller, which then calls ABPeoplePicker. In this demo program, no user intervention is necessary until ABPeoplePicker runs.
The crash occurs if you use the search box in the people picker, and then select one of the resulting people. (If you use the simulator, you'll need to add a person in Contacts before running the program.) The program returns, but during the dismissal of the two modal VCs, I get an assertion error crash. It occurs every time on iphone, ipad, and simulators for both. This seems a very normal thing to do, so I find it hard to believe this is a real bug. The crash message is:
Assertion failure in -[ABMembersSearchDisplayController setActive:animated:], /SourceCache/UIKit_Sim/UIKit-1448.69/UISearchDisplayController.m:589 2011-01-31 13:51:11.903 testcrasher2[26044:207] * Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'search contents navigation controller must not change between -setActive:YES and -setActive:NO'
So to demonstrate, in a new Xcode iPhone Window application, I modify the didFinishLaunchingWithOptions to call my controller. Then I create two VCs as follows. (Note you need to add Addressbook frameworks to the target.) Here's the entire program...
AppDelegate.didFinishLaunchingWithOptions:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
myViewController *detailViewController = [[myViewController alloc] init];
// Set the navigation controller as the window's root view controller and display.
UINavigationController * navController = [[UINavigationController alloc] initWithRootViewController: detailViewController];
self.window.rootViewController = navController;
[self.window makeKeyAndVisible];
[detailViewController release];
[navController release];
return YES;
}
myViewController.h:
@interface myViewController : UIViewController<addDelegate>{
}
@end
myViewController.m:
#import "myViewController.h"
#import "AddNewViewController.h"
@implementation myViewController
- (void)controllerDidFinish:(addNewViewController *)controller {
[self dismissModalViewControllerAnimated:YES];
}
-(void) viewWillAppear:(BOOL)animated {
[super viewWillAppear: animated];
addNewViewController *addController = [[addNewViewController alloc] init];
addController.delegate = self;
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:addController];
[self presentModalViewController:navController animated:YES];
[navController release];
[addController release];
}
@end
AddNewViewController.h:
#import <AddressBookUI/AddressBookUI.h>
@protocol addDelegate;
@interface addNewViewController : UIViewController < ABPeoplePickerNavigationControllerDelegate> {
id <addDelegate> delegate;
}
@property(nonatomic, assign) id <addDelegate> delegate;
@end
@protocol addDelegate <NSObject>
- (void)controllerDidFinish:(addNewViewController *)controller ;
@end
AddNewViewController.m:
#import "AddNewViewController.h"
@implementation addNewViewController
@synthesize delegate;
-(void) viewDidAppear:(BOOL)animated {
ABPeoplePickerNavigationController * peoplepicker = [[ABPeoplePickerNavigationController alloc] init] ;
peoplepicker.peoplePickerDelegate = self;
[self presentModalViewController:peoplepicker animated:YES];
[peoplepicker release];
}
#pragma mark AddressBook delegate methods
- (void)peoplePickerNavigationControllerDidCancel: (ABPeoplePickerNavigationController *)peoplePicker {
[self dismissModalViewControllerAnimated:YES];
}
- (BOOL)peoplePickerNavigationController: (ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person {
[self.delegate controllerDidFinish:self ];
return NO; //EDIT: This MUST be YES or it will crash (see answer below)
}
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person
property:(ABPropertyID)property
identifier:(ABMultiValueIdentifier)identifier {
return NO;
}
@end
Turns out this is an actual bug. It is indeed true that if you do a double ModalVC dismiss to ABPeoplePicker when the user clicks a person in search, you'll get this crash. Fortunately, there's a simple workaround: return YES in your delegate's shouldContinueAfterSelectingPerson. As you're simultaneously dismissing the picker, it doesn't really matter whether you return YES or NO, it won't continue, but NO will crash and YES doesn't. (Same answer as for my original post: Weird crash in ABPeoplePicker )
The bug is in fact in your code. Took me a few minutes to find it, I'll try to explain as best I can.
ABPeoplePickerNavigationController
is currently presented modally.What happens here, is the
ABPeoplePickerNavigationController
asks its delegate (which is youraddNewViewController
) whether it should continue after selecting a person. While it's waiting to hear back from you, you suddenly call your own protocol's method (inmyViewController
) that attempts to dismiss the modaladdNewViewController
. You're jumping ahead of yourself, as theABPeoplePickerNavigationController
is still open.Change your implementation of the ABPeoplePickerNavigationControllerDelegate method to read:
And your crash will go away. When you're dealing with layers upon layers of UIViewControllers and UINavigationControllers, you have to be very careful to dismiss them in the reverse order you presented them.