73

我从UIBarButtonItem使用 Xcode 故事板(所以没有代码)创建了一个弹出框,如下所示:

Xcode 5.0 Connections Inspector with Popover

呈现弹出框效果很好。但是,当我点击使它出现的弹出框时,我无法让它消失。UIBarButtonItem

当按钮被按下(第一次)时,弹出框出现。当再次按下按钮(第二次)时,相同的弹出框出现在其顶部,所以现在我有两个弹出框(如果我继续按下按钮,则有更多)。根据iOS Human Interface Guidelines我需要使弹出框出现在第一次点击并在第二次点击时消失:

确保一次只能在屏幕上看到一个弹出框。您不应同时显示多个弹出框(或旨在外观和行为类似于弹出框的自定义视图)。特别是,您应该避免同时显示层叠或层次结构的弹出框,其中一个弹出框从另一个弹出框出现。

当用户第二次点击时如何关闭弹出框UIBarButtonItem

4

6 回答 6

114

编辑:这些问题似乎已从 iOS 7.1 / Xcode 5.1.1 开始修复。(可能更早,因为我无法测试所有版本。肯定是在 iOS 7.0 之后,因为我测试了那个。)当您从 a 创建弹出框 segue 时UIBarButtonItem,segue 确保再次点击弹出框会隐藏弹出框而不是而不是显示重复。它也适用于UIPresentationControllerXcode 6 为 iOS 8 创建的基于新的 popover segues。

由于我的解决方案可能对那些仍然支持早期 iOS 版本的人具有历史意义,因此我将其留在下面。


如果您存储对 segue 的弹出框控制器的引用,在重复调用 时将其设置为新值之前将其关闭prepareForSegue:sender:,您所避免的只是在重复按下按钮时获得多个堆叠弹出框的问题——您仍然不能使用按照 HIG 的建议(以及在 Apple 的应用程序等中看到的)关闭弹出框的按钮

不过,您可以利用 ARC 将弱引用归零来获得一个简单的解决方案:

1:从按钮开始

从 iOS 5 开始,您无法使用 a 的 segue 进行此UIBarButtonItem操作,但您可以在 iOS 6 及更高版本上使用。(在 iOS 5 上,您必须从视图控制器本身中分离出来,然后performSegueWithIdentifier:在检查弹出框后调用按钮的操作。)

2:使用对弹出框的引用-shouldPerformSegue...

@interface ViewController
@property (weak) UIPopoverController *myPopover;
@end

@implementation ViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // if you have multiple segues, check segue.identifier
    self.myPopover = [(UIStoryboardPopoverSegue *)segue popoverController];
}
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
    if (self.myPopover) {
        [self.myPopover dismissPopoverAnimated:YES];
        return NO;
    } else {
        return YES;
    }
}
@end

3:没有第三步!

在这里使用归零弱引用的好处是,一旦弹出框控制器被关闭——无论是通过编程shouldPerformSegueWithIdentifier:方式,还是通过用户在弹出框之外的其他位置自动点击——ivarnil再次进入,所以我们回到我们的初始状态。

如果不将弱引用归零,我们还必须:

  • myPopover = nil在 中解除它时设置shouldPerformSegueWithIdentifier:,并且
  • 将我们自己设置为弹出框控制器的委托,以便捕获popoverControllerDidDismissPopover:并设置myPopover = nil在那里(因此我们在弹出框自动关闭时捕获)。
于 2012-04-20T00:10:09.133 回答
13

我在这里找到了解决方案https://stackoverflow.com/a/7938513/665396 首先 prepareForSegue:sender: 将指向 UIPopoverController 的指针存储在 ivar/property 中,并使用该指针在后续调用中关闭弹出窗口。

...
@property (nonatomic, weak) UIPopoverController* storePopover;
...

- (void)prepareForSegue:(UIStoryboardSegue *)segue 
                 sender:(id)sender {
if ([segue.identifier isEqualToString:@"My segue"]) {
// setup segue here

[self.storePopover dismissPopoverAnimated:YES];
self.storePopover = ((UIStoryboardPopoverSegue*)segue).popoverController;
...
}
于 2011-11-28T19:51:13.220 回答
2

我为此使用了自定义segue。

1

创建自定义 segue 以在 Storyboard 中使用:

@implementation CustomPopoverSegue
-(void)perform
{
    // "onwer" of popover - it needs to use "strong" reference to retain UIPopoverReference
    ToolbarSearchViewController *source = self.sourceViewController;
    UIViewController *destination = self.destinationViewController;
    // create UIPopoverController
    UIPopoverController *popoverController = [[UIPopoverController alloc] initWithContentViewController:destination];
    // source is delegate and owner of popover
    popoverController.delegate = source;
    popoverController.passthroughViews = [NSArray arrayWithObject:source.searchBar];
    source.recentSearchesPopoverController = popoverController;
    // present popover
    [popoverController presentPopoverFromRect:source.searchBar.bounds 
                                       inView:source.searchBar
                     permittedArrowDirections:UIPopoverArrowDirectionAny
                                     animated:YES];

}
@end

2

在作为 segue 的源/输入的视图控制器中,例如使用操作启动 segue:

-(void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
    if(nil == self.recentSearchesPopoverController)
    {
        NSString *identifier = NSStringFromClass([CustomPopoverSegue class]);
        [self performSegueWithIdentifier:identifier sender:self];
    } 
}

3

引用由创建 UIPopoverController 的 segue 分配 - 当解除弹出窗口时

-(void)searchBarTextDidEndEditing:(UISearchBar *)searchBar
{
    if(self.recentSearchesPopoverController)
    {
        [self.recentSearchesPopoverController dismissPopoverAnimated:YES];
        self.recentSearchesPopoverController = nil;
    }    
}

问候,彼得

于 2012-03-11T14:17:03.780 回答
2

我解决了这个问题,创建了一个ixPopoverBarButtonItem触发 segue 或关闭显示的弹出框的自定义。

我做什么:我切换按钮的动作和目标,所以它要么触发 segue,要么处理当前显示的弹出框。

我花了很多时间在谷歌上搜索这个解决方案,我不想因为切换动作的想法而功劳。将代码放入自定义按钮是我将样板代码保持在最低限度的方法。

在情节提要中,我将 BarButtonItem 的类定义为我的自定义类:

自定义栏按钮

然后我将 segue 创建的弹出框传递给prepareForSegue:sender:方法中的自定义按钮实现:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender  
{
    if ([segue.identifier isEqualToString:@"myPopoverSegue"]) {
        UIStoryboardPopoverSegue* popSegue = (UIStoryboardPopoverSegue*)segue;
        [(ixPopoverBarButtonItem *)sender showingPopover:popSegue.popoverController];
    }
}

顺便说一句...由于我有多个按钮触发弹出框,我仍然必须保留当前显示的弹出框的引用并在我使新的可见时将其关闭,但这不是您的问题...

这是我实现自定义 UIBarButtonItem 的方式:

...界面:

@interface ixPopoverBarButtonItem : UIBarButtonItem

- (void) showingPopover:  (UIPopoverController *)popoverController;

@end

...并暗示:

#import "ixPopoverBarButtonItem.h"
@interface ixPopoverBarButtonItem  ()
@property (strong, nonatomic) UIPopoverController *popoverController;
@property (nonatomic)         SEL                  tempAction;           
@property (nonatomic,assign)  id                   tempTarget; 

- (void) dismissPopover;

@end

@implementation ixPopoverBarButtonItem

@synthesize popoverController = _popoverController;
@synthesize tempAction = _tempAction;
@synthesize tempTarget = _tempTarget;

-(void)showingPopover:(UIPopoverController *)popoverController {

    self.popoverController = popoverController;
    self.tempAction = self.action;
    self.tempTarget = self.target;
    self.action = @selector(dismissPopover);
    self.target = self;
}    

-(void)dismissPopover {
    [self.popoverController dismissPopoverAnimated:YES];
    self.action = self.tempAction;
    self.target = self.tempTarget;

    self.popoverController = nil;
    self.tempAction = nil;
    self.tempTarget = nil;
}


@end

ps:我是ARC新手,所以我不完全确定我是否在这里泄漏。请告诉我,如果我...

于 2012-04-05T17:02:41.280 回答
2

我已经解决了这个问题,无需保留UIPopoverController. 只需处理情节提要中的所有内容(工具栏、BarButtons 等),然后

  • 通过布尔值处理弹出框的可见性,
  • 确保有一个委托,并将其设置为 self

这是所有代码:

视图控制器.h

@interface ViewController : UIViewController <UIPopoverControllerDelegate>
@end

视图控制器.m

@interface ViewController ()
@property BOOL isPopoverVisible;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.isPopoverVisible = NO;
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    // add validations here... 
    self.isPopoverVisible = YES;
    [[(UIStoryboardPopoverSegue*)segue popoverController] setDelegate:self];
}

- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
    return !self.isPopoverVisible;
}

- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
    self.isPopoverVisible = NO;
}
@end
于 2013-11-19T14:03:55.543 回答
1

我把 rickster 的答案打包成一个从 UIViewController 派生的类。此解决方案确实需要以下内容:

  • 带有 ARC 的 iOS 6(或更高版本)
  • 从此类派生您的视图控制器
  • 如果要覆盖这些方法,请确保调用 prepareForSegue:sender 和 shouldPerformSegueWithIdentifier:sender 的“超级”版本
  • 使用命名的弹出框segue

这样做的好处是您不必执行任何“特殊”编码来支持正确处理 Popover。

接口

@interface FLStoryboardViewController : UIViewController
{
    __strong NSString            *m_segueIdentifier;
    __weak   UIPopoverController *m_popoverController;
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender;
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender;
@end

实施

@implementation FLStoryboardViewController

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if( [segue isKindOfClass:[UIStoryboardPopoverSegue class]] )
    {
        UIStoryboardPopoverSegue *popoverSegue = (id)segue;

        if( m_popoverController  ==  nil )
        {
            assert( popoverSegue.identifier.length >  0 );    // The Popover segue should be named for this to work fully
            m_segueIdentifier   = popoverSegue.identifier;
            m_popoverController = popoverSegue.popoverController;
        }
        else
        {
            [m_popoverController dismissPopoverAnimated:YES];
            m_segueIdentifier = nil;
            m_popoverController = nil;
        }
    }
    else
    {
        [super prepareForSegue:segue sender:sender];
    }
}


- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
{
    // If this is an unnamed segue go ahead and allow it
    if( identifier.length != 0 )
    {
        if( [identifier compare:m_segueIdentifier]  ==  NSOrderedSame )
        {
            if( m_popoverController == NULL )
            {
                m_segueIdentifier = nil;
                return YES;
            }
            else
            {
                [m_popoverController dismissPopoverAnimated:YES];
                m_segueIdentifier = nil;
                m_popoverController = nil;
                return NO;
            }
        }
    }

    return [super shouldPerformSegueWithIdentifier:identifier sender:sender];
}

@end

GitHub 上提供的源代码

于 2012-11-03T13:35:42.060 回答