Accessing UIPopoverController for UIActionSheet on iPad
Asked Answered
S

7

16

On the iPad, one can show a UIActionSheet using -showFromBarButtonItem:animated:. This is convenient because it wraps a UIPopoverController around the action sheet and it points the popover's arrow to the UIBarButtonItem that is passed in.

However, this call adds the UIBarButtomItem's toolbar to the list of passthrough views - which isn't always desirable. And, without a pointer to the UIPopoverController, one can't add other views to the passthrough list.

Does anyone know of a sanctioned approach to getting a pointer to the popover controller?

Thanks in advance.

Sherasherar answered 10/5, 2010 at 22:51 Comment(0)
R
10

You would need to adjust on orientation change.

I have found an alternative solution, that works perfectly for me.

Stick to

- (void)showFromBarButtonItem:(UIBarButtonItem *)item animated:(BOOL)animated

In your @interface add

UIActionSheet *popoverActionsheet;

also add

@property (nonatomic, retain) UIActionSheet *popoverActionsheet;

also add

- (IBAction)barButtonItemAction:(id)sender;

So you have reference to your actionsheet from anywhere in your implementation.

in your implementation

- (IBAction) barButtonItemAction:(id)sender
{
    //If the actionsheet is visible it is dismissed, if it not visible a new one is created.
    if ([popoverActionsheet isVisible]) {
        [popoverActionsheet dismissWithClickedButtonIndex:[popoverActionsheet cancelButtonIndex] 
                                                     animated:YES];
        return;
    }

    popoverActionsheet = [[UIActionSheet alloc] initWithTitle:nil
                                                     delegate:self
                                            cancelButtonTitle:nil 
                                       destructiveButtonTitle:nil
                         otherButtonTitles:@"Save Page", @"View in Safari", nil];

    [popoverActionsheet showFromBarButtonItem:sender animated:YES];
}

in your actionsheet delegate implement

- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
{

    if (buttonIndex == [actionSheet cancelButtonIndex]) return;

    //add rest of the code for other button indeces
}

and don't forget to release popoverActionsheet in

- (void)dealloc

I trust that this will do.

Riverhead answered 12/5, 2010 at 11:23 Comment(1)
Stalinkay, thanks. What you describe is close to what I am doing now. I should have been more specific. I have my action sheet shown from a button in a toolbar at the bottom of screen. I also have a navigation bar across the top of the screen. I would like buttons in the nav bar to behave the same (with respect to action sheet) as buttons in toolbar. In other words, I would like nav bar buttons to be on action sheet's popover's passthrough list - and to have taps in nav bar buttons both dismiss action sheet and handle button event. This is why getting pointer to popover is important.Sherasherar
S
9
    if ([[[[actionSheet superview] superview] nextResponder] respondsToSelector:@selector(setPassthroughViews:)]) {
        [[[[actionSheet superview] superview] nextResponder] performSelector:@selector(setPassthroughViews:) withObject:nil];
    }

Will do the trick, this shouldn't cause any problems with the App Reviewers either as its not calling any private APIs.

It is fragile - the if statements ensures your code won't crash (just not work) in the unlikely event Apple change the underlying implementation.

Squamation answered 4/5, 2011 at 11:40 Comment(3)
This is a good solution. I wonder though why Apple adds the bar to the passthroughviews in the first place.Roath
This doesn't work for me on iOS 6. The popover controller is not in the responder chain.Pharisaic
Work great for me on iOS 6! It has to be called after the action sheet is presented.Braxy
R
4

I doubt there's a sanctioned approach to this since actionsheets are presented in UIPopoverViews which is linked to a UIViewController not a UIPopoverController

NSLog(@"%@",[[[popoverActionsheet superview] superview] passthroughViews]);

Looking through the UIPopoverView header file (UNDOCUMENTED) yields the following solution albeit undocumented. Doubt you can snick that past the App Reviewers but give it a go if you think it's worth a shot.

http://github.com/kennytm/iphone-private-frameworks/blob/master/UIKit/UIPopoverView.h

NSArray *passthroughViews = [[[popoverActionsheet superview] superview] passthroughViews];

        NSMutableArray *views = [passthroughViews mutableCopy];
        [views addObject:[self view]];
        [[[popoverActionsheet superview] superview] setPassthroughViews:views];
        [views release];
Riverhead answered 13/5, 2010 at 9:29 Comment(2)
I think that this is what I was looking for - at some level. It is, obviously, fragile to the extent that Apple could change the underlying implementation by, say, adding another layer of views, etc. I am tempted to write my own version with popover. When I get around to that, I will post code here. Thanks, again!Sherasherar
Great. Would like to see what you come up with.Riverhead
R
2

I don't think that's feasible. I was sitting with the same problem.

Use

- (void)showFromRect:(CGRect)rect inView:(UIView *)view animated:(BOOL)animated

Example:

[actionSheet showFromRect:[[[myToolBar subviews] objectAtIndex:0] frame] inView:myToolBar animated:YES];

Note: You must change the index for objectAtIndex: to fit your situation and have reference to your ToolBar

Riverhead answered 11/5, 2010 at 14:45 Comment(1)
Does that work through changes of orientation? Or do you need to adjust on rotation events? Thanks.Sherasherar
S
2

Here is the solution. I just discovered it and it's so easy. Set userInteractionEnabled to NO before showing the action sheet and set it to YES at the end of your action sheet delegate method for dismissal.

- (void)promptResetDefaults:(id)sender
{
    UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:nil
                                                             delegate:self
                                                    cancelButtonTitle:nil
                                               destructiveButtonTitle:NSLocalizedString(@"Reset All Defaults", nil)
                                                    otherButtonTitles:nil];

    self.navigationController.navigationBar.userInteractionEnabled = NO;
    [actionSheet showFromBarButtonItem:sender animated:YES];
}

- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == actionSheet.destructiveButtonIndex)
    {
        [self.dataDefaults resetDataDefaults];
        [self.tableView reloadData];
    }

    self.navigationController.navigationBar.userInteractionEnabled = YES;
}
Streetlight answered 15/4, 2013 at 4:39 Comment(1)
Very well workaround (might even be the best one here). I don't know why people don't upvote this.Utu
G
2

I've been using nbransby's answer for a couple years, but that no longer works in iOS 8. However, the problem still exists with the new UIAlertController in iOS 8. Here's an update that works for me:

UIAlertController *actionSheet = [UIAlertController
    alertControllerWithTitle:@"Test"
    message:@"Hello, world!"
    preferredStyle:UIAlertControllerStyleActionSheet];
[actionSheet addAction:[UIAlertAction
    actionWithTitle:@"OK"
    style:UIAlertActionStyleDefault
    handler:^(UIAlertAction *action) {}]];
actionSheet.modalPresentationStyle = UIModalPresentationPopover;
actionSheet.popoverPresentationController.barButtonItem = testButton;
[self presentViewController:actionSheet animated:TRUE completion:^(void) {
    actionSheet.popoverPresentationController.passthroughViews = [NSArray array];
}];

Note that passthroughViews has to be set after the popover appears, but you can conveniently use the completion block to do that.

Grouper answered 23/12, 2014 at 18:20 Comment(0)
R
1

When presenting from a bar button item, you usually want to disable all other buttons on your toolbar until the action sheet is dismissed.

I think the simplest way to do this is to modify the UIToolbar class. You'll need a way to get hold of the actionsheet, perhaps by saving it in the app delegate.

Make a file UIToolbar+Modal.m, like this:

@implementation UIToolbar (Modal)

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
  UIView *hitView = [super hitTest:point withEvent:event];

  UIActionSheet *mySheet = [[[UIApplication sharedApplication] delegate] myActionSheet];
  if ([mySheet isVisible]) return nil;
  else return hitView;
}

@end

The .h file doesn't need to contain anything special

@interface UIToolbar (Modal) {
}
@end

By the way, before finding this solution I tried assigning an empty array to passthroughViews, that you can access as (originally) described by stalinkay, but this doesn't in fact work (in addition to being undocumented).

The other ways to do this all have disadvantages - either you have to handle orientation changes, or the toolbar buttons look like they press down when in fact the only action taken is to dismiss the actionsheet.

UPDATE

As of iOS 4.3 this no longer works. You need to subclass:

UIToolbarWithModal.h

#import <Foundation/Foundation.h>

@interface UIToolbarWithModal : UIToolbar {
}

@end

Remember when you create the action sheet you need to keep it accessible, perhaps in your app delegate - in this example in the myActionSheet property.

UIToolbarWithModal.m

#import "UIToolbarWithModal.h"
#import "MyDelegate.h"

@implementation UIToolbarWithModal

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
  UIView *hitView = [super hitTest:point withEvent:event];

  UIActionSheet *mySheet = [[[UIApplication sharedApplication] delegate] myActionSheet];
  if ([mySheet isVisible]) return nil;
  else return hitView;
}

@end

Then just use UIToolbarWithModal in your code where you would have used UIToolbar before.

Reticule answered 5/11, 2010 at 17:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.