views:

2200

answers:

6

Hi, I am using the split view template to create a simple split view that has, of course, a popover in Portrait mode. I'm using the default code generated by template that adds/removes the toolbar item and sets the popover controller and removes it. These two methods are splitViewController:willShowViewController:... and splitViewController:willHideViewController:...

I'm trying to figure out how to make the popover disappear if the user taps on the toolbar button while the popover is displayed. You can make the popover disappear without selecting an item if you tap anywhere outside the popover, but I would also like to make it disappear if the user taps the button again.

Where I'm stuck is this: there doesn't seem to be an obvious, easy way to hook into the action for the toolbar button. I can tell, using the debugger, that the action that's being called on the button is showMasterInPopover. And I am new to working with selectors programmatically, I admit.

Can I somehow write an action and set it on the toolbar item without overriding the action that's already there? e.g. add an action that calls the one that's there now? Or would I have to write an action that shows/hides the popover myself (behavior that's being done behind the scenes presumably by the split view controller now???).

Or am I missing an easy way to add this behavior to this button without changing the existing behavior that's being set up for me?

Thank you!

+1  A: 

So, the barButtonItem will have the UISplitViewController as the target and showMasterInPopover: as the action. I can't find it in the documentation, so I'm a bit worried it's not okay to call it, but I got it to work by changing the target to self (the view controller) and the action to a custom method, like this:

- (void)showMasterInPopover:(id)sender {
    // ...insert custom stuff here...
    [splitViewController showMasterInPopover:sender];
}
greenisus
A: 

Elisabeth writes:

You can make the popover disappear without selecting an item if you tap anywhere outside the popover, but I would also like to make it disappear if the user taps the button again.

First of all, let me say that none of what I am about to say is to be taken personally -- it is not meant that way. It all comes from years of designing programming interfaces and studying the Apple Human Interface Guidelines (as well as having a Graphic Designer who is contstantly trying to teach me the right way to do things). It is meant as an opposing viewpoint and not as a rant.

What you are suggesting is a problem UI-wise for me, and will be an issue that causes trouble when Apple reviews the app. You are never supposed to have a known-UI-object perform a function that it does not perform normally (For instance: a button never shows and then releases a view/object/window. Toggles do this).

For instance, a magnifying glass on the navbar means Search (as defined by Apple). They have in the past, and will continue in the future to, refuse apps that use this for zooming the interface. For example: Apple Rejects ConvertBot or The Odyssey: Trail of Tears (search the page for it). The language in the rejection is always the same (bold marking what they would cite for your usage):

“… uses standard iPhone/iPod screen images in a non-standard way, potentially resulting in user confusion. Changing the behavior of standard iPhone graphics, actions, and images, or simulating failures of those graphics, actions, or images is a violation of the iPhone Developer Program agreement which requires applications to abide by the Human Interface Guidelines.”

Also, if you really want this feature, ask yourself: "Why?". If it is because you, yourself, like it, then I would really skip it. Most users would be confused by this behavior and would not actually use it because they would not know it was an option to use. Apple spent the last 3 years training iPhoneOS users how to use their OS and interface elements. The last thing you, as a programmer or designer, want to do is spend time trying to train a user on how to use your app. They will generally remove your app from their device and move to another similar app instead of forcing themselves to learn your way of doing things.

Just my $.02

Jann
This behavior is pretty standard I've seen it on a lot of apps (as well as Apple's web-app documentation made to look like a popover - it does this too).
Elisabeth
gerry3
A: 

Don't have the rep to make a real comment. :-(

@Jann - I'm pretty sure what Elizabeth wants to do is pretty standard. For example, the Notes application that ships pre-loaded on the iPad closes and opens the popover when you press the toolbar button in the top left corner.

speckledcarp
A: 

Below is my solution. It starts out similar to greenisus' solution, by hooking the UISplitViewController's toolbar button event handler. I use a flag in my controller to track whether the popover is open or not. Finally, to handle the case where the user opens the popover, then closes it by clicking outside the popover, I implement the UIPopoverControllerDelegate protocol.

First, the controller interface:

@interface LaunchScene : NSObject <UISplitViewControllerDelegate, UIPopoverControllerDelegate>
{
    UISplitViewController* _splitViewController;    //Shows list UITableView on the left, and details on the right
    UIToolbar* _toolbar;                            //Toolbar for the button that will show the popover, when in portrait orientation
    SEL _svcAction;                                 //The action from the toolbar
    id _svcTarget;                                  //The target object from the toolbar
    UIPopoverController* _popover;                  //The popover that might need to be dismissed
    BOOL _popoverShowing;                           //Whether the popover is currently showing or not
}

-(void) svcToolbarClicked: (id)sender;

I use _svcAction and _svcTarget to addess greenisus' worries that he might not be calling the right function.

Below is my implementation. For brevity, I have omitted the code that instantiates the UISplitViewController and the subviews. All the show/hide related code is shown.

//the master view controller will be hidden so hook the popover
- (void)splitViewController:(UISplitViewController*)svc willHideViewController:(UIViewController *)aViewController withBarButtonItem:(UIBarButtonItem*)barButtonItem forPopoverController:(UIPopoverController*)pc 
{   
    _popoverShowing = FALSE;
    if(_toolbar == nil) 
    {
        //set title of master button
        barButtonItem.title = @"Title goes here";

        //Impose my selector in between the SVController, and the SVController's default implementation
        _svcTarget = barButtonItem.target;
        _svcAction = barButtonItem.action;
        barButtonItem.target = self;
        barButtonItem.action = @selector(svcToolbarClicked:);

        //create a toolbar
        _toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, 1024, 44)];
        [_toolbar setItems:[NSArray arrayWithObject:barButtonItem] animated:YES];
    }

    //add the toolbar to the details view (the second controller in the splitViewControllers array)
    UIViewController* temp = [_splitViewController.viewControllers objectAtIndex:1];
    [temp.view addSubview:_toolbar];
}

Here is my function, that responds to the toolbar click. This handles the case where the user taps and re-taps the toolbar button.

-(void) svcToolbarClicked: (id)sender
{
    if(_popoverShowing)
    {
        [_popover dismissPopoverAnimated:TRUE];
    }
    else 
    {
        //Perform the default SVController implementation
        [_svcTarget performSelector:_svcAction];
    }
    //Toggle the flag
    _popoverShowing = !_popoverShowing;
}

Some functions from UISplitViewControllerDelegate

//the master view (non-popover) will be shown again (meaning it is going to landscape orientation)
- (void)splitViewController:(UISplitViewController*)svc willShowViewController:(UIViewController *)aViewController invalidatingBarButtonItem:(UIBarButtonItem *)button 
{
    //remove the toolbar
    [_toolbar removeFromSuperview];
}

// the master view controller will be displayed in a popover (i.e. the button has been pressed, and the popover is about to be displayed.  
//Unfortunately triggers when the popover is ALREADY displayed.
- (void)splitViewController:(UISplitViewController*)svc popoverController:(UIPopoverController*)pc willPresentViewController:(UIViewController *)aViewController 
{   
    _popover = pc; //Grab the popover object  
    _popover.delegate = self;
}

The above code is sufficient for most cases. However, if the user opens the popover, then dismisses by clicking elsewhere on the screen, the _popoverShowing boolean will contain an incorrect value, which will force the user to tap the toolbar button twice to re-open the popover. To fix this, implement the UIPopoverControllerDelegate method, like the snippet below.

//UIPopoverControllerDelegate method
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController
{
    _popoverShowing = FALSE;
    _popover = nil;
}

This took me forever to figure out, digging through the docs and (I think) most of the UISplitViewController questions on StackOverflow. I hope somebody finds it useful. If so, I covet reputation points. ;-)

speckledcarp
Wow, thank you! I will definitely try this out and let you know if it works :-)
Elisabeth
+5  A: 

So it turns out that you can make the popover dismiss when clicking on the barButtonItem by implementing the SplitViewController willPresentViewController method as follows:

- (void) splitViewController:(UISplitViewController *)svc 
           popoverController: (UIPopoverController *)pc
   willPresentViewController: (UIViewController *)aViewController
{
    if (pc != nil) {
        [pc dismissPopoverAnimated:YES];
    }
}
Elisabeth
A: 

Maybe you all just complicate it too much or I have read something very different than you guys wanted to do... but perhaps, this is what you were all trying to figure out so hardish:

-(void)togglePopOverController {

if ([popOverController isPopoverVisible]) {

[popOverController dismissPopoverAnimated:YES];

} else {

[popOverController presentPopoverFromBarButtonItem:bbiOpenPopOver permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];

}

}
Janos
Well the tricky part is figuring out where to hook into the splitViewController delegate methods. See my solution below.
Elisabeth