Crash in ABPeoplePicker when called from another modal viewcontroller and both dismissed - iphone

(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.
Your
ABPeoplePickerNavigationController
is currently presented modally.
You click in the search bar and type
some stuff.
You click a person's name.
What happens here, is the ABPeoplePickerNavigationController asks its delegate (which is your addNewViewController) 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 (in myViewController) that attempts to dismiss the modal addNewViewController. You're jumping ahead of yourself, as the ABPeoplePickerNavigationController is still open.
Change your implementation of the ABPeoplePickerNavigationControllerDelegate method to read:
- (BOOL)peoplePickerNavigationController: (ABPeoplePickerNavigationController *)peoplePicker
shouldContinueAfterSelectingPerson:(ABRecordRef)person {
// This line is new.
[self.navigationController dismissModalViewControllerAnimated:YES];
[self.delegate controllerDidFinish:self];
return NO;
}
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.

Related

iOS AddressBookUI - 'Shouldn't be trying to show more than one Add to Existing Contact people picker.'

I'm trying add some addressbook functionality to my app, but after tap the second time to the 'Add to existing' button I get this error message.
* Assertion failure in -[ABPersonViewControllerHelper presentPeoplePickerNavigationControllerForAddToContacts:], /SourceCache/AddressBookUI_Sim/AddressBookUI-1118/ABPersonViewControllerHelper.m:2574
2011-10-24 20:41:11.960 locato[4576:11603]
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Shouldn't be trying to show more than one Add to Existing Contact people picker.'
First throw call stack:
(0x152e052 0x16bfd0a 0x14d6a78 0xefd2db 0x9d674 0x152fe72 0xd0eb8 0xc8141 0xb9e2f 0x60471d 0x604952 0xe8c86d 0x1502966 0x1502407 0x14657c0 0x1464db4 0x1464ccb 0x17eb879 0x17eb93e 0x574a9b 0x22d9 0x2255)
terminate called throwing an exception
I have a viewcontroller and a navigationcontroller, and im displaying the addressbook's 'modal' views on the main viewcontroller. (or else im not getting the sliding transition, and this error.). I have overridden these methods:
#implementation UIViewController (cancelButton)
- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker;
{
UIResponder *responder = self;
while (responder && ![responder isKindOfClass:[mainViewController class]]) {
responder = [responder nextResponder];
}
[(UIViewController *)responder dismissModalViewControllerAnimated:YES];
}
#end
#implementation UINavigationController (modal)
- (void) presentModalViewController:(UIViewController *)screen animated:(BOOL)animated
{
UIResponder *responder = self;
while (responder && ![responder isKindOfClass:[mainViewController class]]) {
responder = [responder nextResponder];
}
if ([screen isKindOfClass:[ABPeoplePickerNavigationController class]]) {
ABPeoplePickerNavigationController *scr = screen;
scr.peoplePickerDelegate = self;
[scr release];
}
[(UIViewController *)responder presentModalViewController:screen animated:YES];
}
Im hoping to get some clarification on what im not doing right!
thanks,
David
Two things you could change before trying again:
after both while loops, check that responder is not nil before doing dismiss or present a viewcontroller
can't you just assign screen.peoplePickerDelegate = self; instead of using this scr?

Using UIButtons to open a different .nib

something isn't working right and i can't work out why this isn't working to load my other nib, this currently works
#pragma mark - Flipside View
- (void)flipsideViewControllerDidFinish:(FlipsideViewController *)controller
{
[self dismissModalViewControllerAnimated:YES];
}
- (IBAction)showInfo:(id)sender
{
FlipsideViewController *controller = [[FlipsideViewController alloc] initWithNibName:#"FlipsideViewController" bundle:nil];
controller.delegate = self;
controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:controller animated:YES];
}
whilst i've done the exact same thing here on the same MainViewController.m and had no success whilst doing it
#pragma mark - News View
- (void)newsViewControllerDidFinish:(NewsViewController *)controller
{
[self dismissModalViewControllerAnimated:YES];
}
- (IBAction)showNews:(id)sender
{
NewsViewController *controller = [[NewsViewController alloc] initWithNibName:#"NewsViewController" bundle:nil];
controller.delegate = self;
controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:controller animated:YES];
}
so i think there is something wrong with my header file which looks like this
#import "FlipsideViewController.h"
#import "NewsViewController.h"
#interface MainViewController : UIViewController <FlipsideViewControllerDelegate, NewsViewControllerDelegate>
- (IBAction)showInfo:(id)sender;
- (IBAction)showNews:(id)sender;
#end
I can't work out why it's not working, any help would be appreciated.
Error from output:
This GDB was configured as "x86_64-apple-darwin".Attaching to
process 2147.
2011-07-08 12:24:09.845 Danny[2147:ef03] -[NewsViewController
setDelegate:]: unrecognized selector
sent to instance 0x68a62b0
2011-07-08 12:24:09.847 Danny[2147:ef03] * Terminating app
due to uncaught exception
'NSInvalidArgumentException', reason:
'-[NewsViewController setDelegate:]:
unrecognized selector sent to instance
0x68a62b0'
* First throw call stack:
(0xf8a600 0x112252e 0xf8d550 0xeeb4cf 0xeeb292 0x2a36 0xf8bd78
0x18cc5 0x18c5a 0xbdbd4 0xbe09d
0xbd368 0x3e004 0x3e22d 0x24990
0x181a7 0x1369886 0xf59d11 0xebbc9b
0xeba4b1 0xeb993c 0xeb9868 0x1367fef
0x13680b4 0x160c4 0x2009 0x1f75)
terminate called throwing an exceptionsharedlibrary
apply-load-rules all
Current language: auto; currently objective-c
(gdb)
What is the delegate method for the view controller? Check if the declaration of the delegate method is the same. Make sure you have set the delegates properly
Try this:
- (IBAction)showNews:(id)sender
{
NewsViewController *controller = [[NewsViewController alloc] initWithNibName:#"NewsViewController" bundle:nil];
controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:controller animated:YES];
}
And in the News View, instead of writing
[self.delegate newsViewControllerDidFinish:self];
you should write:
[self dismissModalViewControllerAnimated:YES];
That should solve your problem. I don't see much point in writing extra delegates, they don't usually give you any advantages (in scenarios like this one), when you can just have a view controller dismiss itself.

Why is My Application Crashing When I Call a Method in self.parentViewController?

In my code I'm doing the following:
-(void)pushCropImageViewControllerWithDictionary:(NSDictionary *)dictionary {
civc = [[CropImageViewController alloc] init];
[self presentModalViewController:civc animated:YES];
civc.myImage.image = [dictionary objectForKey:UIImagePickerControllerOriginalImage];
}
So I have a modal view in my app. When this modal view dismisses, I want to call a method from the parent view (the view that called pushCropImageViewControllerWithDictionary), like this:
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillAppear:animated];
[(AddNewItemViewController *)self.parentViewController addCroppedPicture:screenshot];
}
But it keeps crashing with the following message:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UITabBarController addCroppedPicture:]: unrecognized selector sent to instance 0x4d15930'
Can someone tell me what am I doing wrong? I am including the header for AddNewItemViewController so the selector should be recognized. Can someone give me a hand on how can I do this properly? Thanks.
EDIT: Declaration of addCroppedPicture:
-(void)addCroppedPicture:(UIImage *)image;
The implementation itself is empty so far.
Apparently, self.parentViewController isn't an instance of AddNewItemViewController but the tab bar controller. Hence the crash.
The proper solution is to make a delegate:
-(void)pushCropImageViewControllerWithDictionary:(NSDictionary *)dictionary
{
civc = [[CropImageViewController alloc] init];
civc.delegate = self;
[self presentModalViewController:civc animated:YES];
...
}
To send a message back to the delegate:
-(void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.delegate addCroppedPicture:screenshot];
}
It's up to you do declare the delegate protocol:
#protocol CropImageDelegate <NSObject>
- (void)addCroppedPicture:(UIImage*)image;
#end
And add a property for the delegate to CropImageViewController:
#property (nonatomic, assign) id<CropImageDelegate> delegate;
And finally, make your view controller conform to this delegate protocol.

How to get started coding In-App Purchase

I am working on in-app purchase. In my application we added the following code in appdelegate:
#import "InappPurchaseAppDelegate.h"
#import "MainController.h"
#import "MKStoreManager.h"
#import "MKStoreObserver.h"
#implementation InappPurchaseAppDelegate
#synthesize window;
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
[MKStoreManager sharedManager];
navigationController = [[UINavigationController alloc] init];
[window addSubview:navigationController.view];
MainController *frontController =[[MainController alloc] init];
[navigationController pushViewController:frontController animated:NO ];
[frontController release]; // Override point for customization after application launch
[window makeKeyAndVisible];
}
and added the following code in our controller:
#import "MainController.h"
#import "MKStoreManager.h"
#import "MKStoreObserver.h"
#import "InappPurchaseAppDelegate.h"
#implementation MainController
-(IBAction)InappPurchase:(id)sender
{
[[MKStoreManager sharedManager] buyFeatureA];
}
I also added storekit framework but when the button is clicked nothing happens.
All you need to know is here: http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Overview%20of%20the%20Store%20Kit%20API/OverviewoftheStoreKitAPI.html#//apple_ref/doc/uid/TP40008267-CH100-SW1
It shouldn't take you more than half a day to implement it (maybe a bit more if the content resides on your servers and is not already in the bundle).
The simplest explaination is that your button is not properly configured to send the action message. To test either set a breakpoint for the method or log it like:
-(IBAction)InappPurchase:(id)sender
{
NSLog(#"Buyid method called");
[[MKStoreManager sharedManager] buyFeatureA];
}
If the NSLog or breakpoint are never hit, you need to check the button in Interface Builder and make such it's action is set to the InappPurchase method.
If the InappPurchase method is being called by the button then the problem is in the MKStoreManger object.

How to correctly use ABPersonViewController with ABPeoplePickerNavigationController to view Contact information?

Update 9/2/10: This code no longer works as of the iOS 4 update--it fails with an internal assertion. There is now a great Address Book API example available in the iPhone SDK Reference Library called "QuickContacts."
The example code is available here; it will help you solve this problem:
http://developer.apple.com/iphone/library/samplecode/QuickContacts/Introduction/Intro.html
I'm attempting to add a feature to my app that allows the user to select a contact from an ABPeoplePickerNavigationController, which then displays an ABPersonViewController corresponding to the contact they picked. At that point, I want the user to be able to click on a contact's phone number and have my app respond with custom behavior.
I've got the ABPeoplePickerNavigationController working fine, but I'm running into a problem displaying the ABPersonViewController. I can get the ABPersonViewController to animate onto the screen just fine, but it only displays the contact's photo, name, and company name. None of the contact's other fields are displayed.
I'm using the 'displayedProperties' element in the ABPersonViewController to tell the program to display phone numbers. This creates some strange behavior; when I select a contact that has no phone numbers assigned, the contact shows up with "No Phone Numbers" written in the background (as you'd expect), but when selecting a contact that does have a phone number, all I get is a blank contact page (without the "No Phone Numbers" text).
Here's the method in my ABPeoplePickerNavigationController delegate class that I'm using to create my PersonViewController class, which implements the ABPersonViewController interface:
- (BOOL) peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person {
BOOL returnState = NO;
PersonViewController *personView = [[PersonViewController alloc] init];
[personView displayContactInfo:person];
[peoplePicker pushViewController:personView animated:YES];
[personView release];
return returnState;
}
Here's my PersonViewController.h header file:
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <AddressBookUI/AddressBookUI.h>
#interface PersonViewController : UIViewController <ABPersonViewControllerDelegate>
{
}
- (void) displayContactInfo: (ABRecordRef)person;
#end
Finally, here's my PersonViewController.m that's creating the ABPersonViewController to view the selected contact:
#import "PersonViewController.h"
#implementation PersonViewController
- (void) displayContactInfo: (ABRecordRef)person
{
ABPersonViewController *personController = [[ABPersonViewController alloc] init];
personController.personViewDelegate = self;
personController.allowsEditing = NO;
personController.displayedPerson = person;
personController.addressBook = ABAddressBookCreate();
personController.displayedProperties = [NSArray arrayWithObjects:
[NSNumber numberWithInt:kABPersonPhoneProperty],
nil];
[self setView:personController.view];
[[self navigationController] pushViewController:personController animated:YES];
[personController release];
}
- (BOOL) personViewController:(ABPersonViewController*)personView shouldPerformDefaultActionForPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifierForValue
{
return YES;
}
#end
Does anyone have any idea as to why I'm seeing this blank Contact screen instead of one with clickable phone number fields?
Alright, I think I'm getting closer to finding the problem. I'm fairly sure it's with this part of the displayContactInfo call above:
[self setView:personController.view];
When I omit this line, all I see is a blank screen when I click a contact. The ABPeoplePickerNavigationController is pushing the PersonViewController onto the NavigationController stack. The PersonViewController then instantiates an ABPersonViewController object, but for whatever reason the ABPersonViewController never gets properly added to the NavigationController stack.
Does anyone know how to add the ABPersonViewController to the stack, rather than just the PersonViewController?
Just a heads-up to anyone who runs into this problem themselves: I was able to product the correct behavior by instantiating the ABPersonViewController in its delegate's viewDidLoad() method as below:
As before, here's my ABPeoplePickerNavigationController delegate's method:
- (BOOL) peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person
{
BOOL returnState = NO;
PersonViewController *personView = [[PersonViewController alloc] init];
[peoplePicker pushViewController:personView animated:YES];
[personView displayContactInfo:person];
[personView release];
return returnState;
}
Here's my PersonViewController.h (ABPersonViewController delegate) header file:
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <AddressBookUI/AddressBookUI.h>
#interface PersonViewController : UIViewController <ABPersonViewControllerDelegate>
{
ABPersonViewController *personController;
}
- (void) displayContactInfo: (ABRecordRef)person;
#end
Finally, here's the delegate's implementation (PersonViewController.m):
#import "PersonViewController.h"
#implementation PersonViewController
- (void) viewDidLoad
{
}
- (void) viewDidUnload
{
[personController release];
}
- (void) displayContactInfo: (ABRecordRef)person
{
personController = [[ABPersonViewController alloc] init];
[personController setDisplayedPerson:person];
[personController setPersonViewDelegate:self];
[personController setAllowsEditing:NO];
personController.addressBook = ABAddressBookCreate();
personController.displayedProperties = [NSArray arrayWithObjects:
[NSNumber numberWithInt:kABPersonPhoneProperty],
nil];
[self setView:personController.view];
}
- (BOOL) personViewController:(ABPersonViewController*)personView shouldPerformDefaultActionForPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifierForValue
{
// This is where you pass the selected contact property elsewhere in your program
[[self navigationController] dismissModalViewControllerAnimated:YES];
return NO;
}
#end
Hopefully this ends up being helpful for someone. The AddressBook UI framework was a bit tricky for me to wrap my head around (although I'm new to iPhone development so I'm still learning a lot of the nuances of iPhone program organization).