Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

ataylor2009

macrumors member
Original poster
Jan 27, 2009
78
0
I've got a view hierarchy that looks like this:

UINavigationController (navigationController)
---> UITableViewController (rootViewController)
------> ABPeoplePickerNavigationController (peoplePicker)
---------> UINavigationController (navCon)
------------> UIViewController (ruleBuilder)

App flow logic is: App starts, user presented with table view of data, user selects 'add' button, app presents peoplePicker, user selects contact, users selects contact property, peoplePicker passes contact info to ruleBuilder, user adjusts some parameters, user taps 'save.' At this point, the Core Data stack creates a new entity, sets the attributes and saves the entity to the store.

When that is done, I need to dismiss all the view controllers and return the user to rootViewController, which I assume means using

Code:
[navigationController popToRootViewControllerAnimated:YES];

but I can't figure out how to get a reference to navigationController into ruleBuilder. It's declared as an IBOutlet in the app delegate. I tried

Code:
[[[UIApplication sharedApplication] delegate].navigationController
popToRootViewControllerAnimated:YES];

but all that got me was an error message about a nonexistent getter method. If I call

Code:
[super dismissModalViewControllerAnimated:YES];

then the ruleBuilder is dismissed (great!) but the peoplePicker is now the top view (not great!) instead of the rootViewController. So, I tried adding

Code:
[self dismissModalViewControllerAnimated:YES];

to the peoplePicker method, at the very end, just before returning - but that didn't work either. Actually crashed the app.

A shove in the right direction would be much appreciated.
 
A couple of concerns:

1) Are you not dismissing the ABPeoplePickerNavigationController when the user selects the contact property? That is normally how I've seen it done.

2) Are you sure you need a second UINavigationController for the ruleBuilder? Why not use the navigation hierarchy of the first one and just push ruleBuilder onto the stack?
 
After much trial and error, what I have is this:

In the peoplePicker:shouldContinueAfterSelectingPerson:property:identifier: method, I grab the selected contact's information and pass it to the ruleBuilder. The only way I could get that to work was to instantiate a new navigation controller from within the peoplePicker, and pass ruleBuilder in as the root view. For more on that whole process, see https://devforums.apple.com/thread/44240?tstart=0.

Sorry to bounce you over there, but it seemed like a better idea than cutting and pasting a whole mess of posts to answer your question. Short version is, me and several other people couldn't find another way to make it work, so I'm doing it this way and trying to figure out how to pop back to the root view. I would love not to have a second navigation controller; my original goal was to have the main navigation controller handle all of this, but I've never gotten that worked out.

Thanks for your help.
 
I hadn't noticed that you've got two nav controllers. Not a good idea.

I agree with dejo on both points.

Without more details or screen shots it's difficult to say with certainty but I think you should pop the people picker when it's done and push the rulebuilder right away.

Also, you can easily post a notification from your leaf view controller that is received by the root view controller and the root view controller can then pop to itself. However, if all you've got is the root and the ruleBuilder then the ruleBuilder can pop itself and you're at the root.
 
Popping the peoplePicker followed by an immediate call to push the ruleBuilder doesn't work; neither of them gets shown, and the app crashes.

Here's the text of my original post:

I am working on a navigation-based utility app, and I need some fresh input on the flow of the application. The root view controller is a table view, from which users can select a row to have the app perform its main function, tap a detail disclosure button to edit the settings for that function, or tap an add button in the navigation bar to add a new item to the table view.

Tapping the add button presents the ABPeoplePickerNavigationController, from which the user navigates to a contact record and then selects one of the contact's properties. It is at this point that things start to get wonky. In order for the selected property to be used in the app, the user needs to set some parameters. Upon selecting the contact's property, the user is sent directly from the people picker to the view where the parameters are set. The only way I could make this work was to instantiate a second navigation controller from within the shouldContinueAfterSelectingPerson:property:identifier: method, instantiating the parameter setting view (call it ruleBuilder) in the same method, and pushing the new navigation controller with the ruleBuilder as the root view.

So, at this point, I've got a navigation controller controlling a table view controlling the people picker controlling a navigation controller controlling the ruleBuilder. When the user finishes setting parameters and taps save, I'd like to save all the data (app uses Core Data) - the contact, selected property, and adjusted parameters - and return the user to the root view (the table view first shown when the app starts). The crux of my issue is the new navigation controller pushed from the people picker. This seems like a clunky implementation. It seems like it would be better to present the people picker, then capture the selected contact and property (say, record ID and address), then dismiss the people picker and immediately push the ruleBuilder from the main navigation controller, using the record ID and address info to set properties in the ruleBuilder (so the appropriate fields in that view can be prepopulated with the correct data).

However, I haven't been able to make this work. In my addContact method, which is where the people picker is instantiated and pushed, I have this:

Code:
ABPeoplePickerNavigationController *picker = [[ABPeoplePickerNavigationController alloc] init];
picker.peoplePickerDelegate = self;
[self presentModalViewController:picker animated:YES];
[picker release];
RuleBuilder *ruleBuilder = [[RuleBuilder alloc] initWithNibName:ruleBuilder bundle:nil];
ruleBuilder.contactID = contactID; // contactID is declared as @property (nonatomic, retain) NSNumber *contactID;
[self presentModalViewControlelr:ruleBuilder animated:YES];
[ruleBuilder release];

In the shouldContinueAfterSelectingPerson:property:identifier: method, I have this:

Code:
contactID = [NSNumber numberWithInt:ABRecordGetRecordID (person)];
...
return NO;

So, what I expected to happen was user taps add > goes to people picker > selects contact > selects property > picker sets contactID = recordID and is then dismissed because of 'return NO;' > control returns to addContact method (which called the people picker) > ruleBuilder is instantiated > ruleBuilder.contactID is set to contactID > ruleBuilder is pushed > program flow continues...

However,

Code:
NSLog(@"The contactID is %@.", contactID);

inserted at the beginning of the ruleBuilder code simply returns "The contactID is (null)." The ruleBuilder view is never presented.

I would love to hear some fresh opinions on this setup. As usual, thanks in advance for your input.
 
And here is a follow-up, a few posts later in the same thread:

Okay...just got home a little while ago, and I finally got a few minutes to try this solution out. Short answer is, no change in my problem. From my root view controller, when the 'add' button is tapped, the application immediately quits, with an NSLog statement of "The recordID is (null)." No other info in the console.

I don't understand why the people picker isn't being called. Here's the first method, that should call the people picker when the 'add' button is tapped:

Code:
- (void)showPeoplePicker {
     ABPeoplePickerNavigationController *picker = [[ABPeoplePickerNavigationController alloc] init];
     picker.peoplePickerDelegate = self;
     [self presentModalViewController:picker animated:YES];
     [picker release];
     
     RuleBuilder* ruleBuilder = [[RuleBuilder alloc] initWithNibName:@"RuleBuilder" bundle:nil];
     ruleBuilder.recordID = self.recordID;
     [self presentModalViewController:ruleBuilder animated:YES];
     [ruleBuilder release];}

And here's the second method, that should set the recordID property:

Code:
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker
       shouldContinueAfterSelectingPerson:(ABRecordRef)person
                                        property:(ABPropertyID)property
                                     identifier:(ABMultiValueIdentifier)identifier {
     self.recordID = [NSNumber numberWithInt:ABRecordGetRecordID(person)];
     return NO; }
 
That's not what I suggested.

You push the people picker. The user chooses something. The peoplepicker goes away. Then you push the ruleBuilder if their choice was valid.

If you need to use a performSelector:afterDelay: to push the ruleBuilder shortly after the peoplepicker goes away.
 
That's not what I suggested.

You push the people picker. The user chooses something. The peoplepicker goes away. Then you push the ruleBuilder if their choice was valid.

If you need to use a performSelector:afterDelay: to push the ruleBuilder shortly after the peoplepicker goes away.

See above. That does not work.

Code:
- (void)showPeoplePicker {
     ABPeoplePickerNavigationController *picker = [[ABPeoplePickerNavigationController alloc] init];
     picker.peoplePickerDelegate = self;
     [self presentModalViewController:picker animated:YES];
     [picker release];
     
     RuleBuilder* ruleBuilder = [[RuleBuilder alloc] initWithNibName:@"RuleBuilder" bundle:nil];
     ruleBuilder.recordID = self.recordID;
     [self presentModalViewController:ruleBuilder animated:YES];
     [ruleBuilder release];}

This causes the app to immediately stop working, without displaying either view.
 
We keep telling you to present the ruleBuilder after selecting a person but you keep showing us code where you present it at the same time as showing the people picker. That is, you're not doing it in the proper method.
 
And I keep saying, "See above." I have done that. That is how I got to where I am now. The code you are referring to was in response to somebody else's suggestion, I tried it and it didn't work, so I went back to the way I had originally been doing it - which was to call the peoplePicker, then from within the peoplePicker method, call the ruleBuilder and pass it the contact's information.

My problem still remains - I cannot figure out how to call the ruleBuilder from within the peoplePicker without instantiating a new navigation controller and using ruleBuilder as its root view.

After much trial and error, what I have is this:

In the peoplePicker:shouldContinueAfterSelectingPersonroperty:identifier: method, I grab the selected contact's information and pass it to the ruleBuilder. The only way I could get that to work was to instantiate a new navigation controller from within the peoplePicker, and pass ruleBuilder in as the root view. For more on that whole process, see https://devforums.apple.com/thread/44240?tstart=0.

Sorry to bounce you over there, but it seemed like a better idea than cutting and pasting a whole mess of posts to answer your question. Short version is, me and several other people couldn't find another way to make it work, so I'm doing it this way and trying to figure out how to pop back to the root view. I would love not to have a second navigation controller; my original goal was to have the main navigation controller handle all of this, but I've never gotten that worked out.

Sorry for the disconnect; I know you're trying to help, but I have literally already been down this path and that's how I got where I am. I either need help making my cobbled-together version work, or I need someone who knows more about it than I do to point out where I've gone wrong in getting to where I am. I think I've posted all the relevant code in the various posts above, but if not (or if you want it all together in one post), let me know and I'll gladly put it up.
 
And I keep saying, "See above." I have done that.
Doing this:
Code:
[self presentModalViewController:ruleBuilder animated:YES];
from within showPeoplePicker is not at all what I'm suggesting. What I am suggesting is that at some point after shouldContinueAfterSelectingPerson:, once the people picker is dismissed, you should be in a position to push ruleBuilder onto the navigation stack.
 
Okay, but where? I need the ruleBuilder to be pushed as soon as the peoplePicker is dismissed, without further interaction from the user. This is the part that I do not understand.
 
OK, now we're getting somewhere. This is essentially a state machine. But since there are really only a couple states we'll use a BOOL to decide if the ruleBuilder should be shown.

Like I said these are the steps:

You push the people picker.
The user chooses something.
[If it's valid we set a BOOL to YES.] added
The peoplepicker goes away.
Then you push the ruleBuilder if their choice was valid.[if the BOOL is YES]

When the peoplepicker goes away various things happen. Its dealloc method is called. viewWillAppear is called on the view controller under it. The navcontroller posts a callback to its delegate. You might be able to use any of these to push the ruleBuilder. Let's try viewWillAppear. So in your viewWillAppear method for the rootviewcontroller you check your mustShowRuleBuilder BOOL and then push the ruleBuilder if required (remember to set it to NO after this).

That should do it.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.