如何消除情节提要Popover


73

我已经UIBarButtonItem使用Xcode故事板创建了一个弹出窗口(因此没有代码),如下所示:

带有Popover的Xcode 5.0 Connections Inspector

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

第一次按下该按钮时,将显示弹出窗口。当再次按下按钮时(第二次),相同的弹出式窗口会显示在其顶部,因此现在我有两个弹出式窗口(如果继续按下该按钮,则更多。)根据iOS人机界面指南,我需要使弹出窗口在第一次点击时出现,而在第二次点击时消失:

确保一次只能在屏幕上看到一个弹出窗口。您不应同时显示多个弹出框(或旨在看起来和行为类似于弹出框的自定义视图)。特别是,应避免同时显示级联或层次结构的弹出窗口,在这种情况下,一个弹出窗口会从另一个弹出窗口中出现。

当用户UIBarButtonItem第二次点击弹出窗口时,如何消除弹出窗口?


您是如何创建segue的?segue的源端是按钮还是视图控制器?您为segue设置了任何直通吗?
rob mayoff

@rob我通过“界面”构建器创建了Segue。我将选择按钮,然后将Popover Segue拖到所需的主视图中。上图显示了这一点。我不确定您在评论中的最后两个问题是什么意思。
山姆·斯宾塞

2
我使用“单视图应用程序”模板和情节提要创建了一个新项目。我将按钮拖动到模板的视图,然后拖出第二个视图控制器。我从按钮开始拖动到第二个VC,然后选择Popover。运行此命令时,我可以触摸按钮以显示弹出窗口,然后当我触摸弹出窗口之外的任何位置(包括按钮上的内容)时,弹出窗口就会消失。你做了什么不同的事情?
rob mayoff

我同意以上评论。再次点击该按钮可关闭弹出窗口。
回忆录2012年

2
@robmayoff您是正确的,它可以与UIButton一起正常工作,但是请对工具栏中的UIBarButton项尝试相同的步骤。一开始我也没有看到,所以我编辑了RazorSharp的问题以使其更加清晰。
马特·安德森

Answers:


114

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

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


如果存储对segue的popover控制器的引用,请在重复调用时将其关闭,然后再将其设置为新值,然后将其关闭,则prepareForSegue:sender:可以避免的问题是反复按下按钮时会出现多个堆叠弹出式窗口的问题-您仍然无法使用该按钮可按照HIG的建议(以及在Apple的应用程序等中看到的)消除弹出式窗口

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

1:从按钮选择

从iOS 5开始,您无法通过使用来执行此操作UIBarButtonItem,但是可以在iOS 6及更高版本上使用。(在iOS 5上,您必须从视图控制器本身进行筛选,然后performSegueWithIdentifier:在检查弹出窗口后调用按钮的action 。)

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:,还是由用户点击弹出窗口外部的其他位置自动关闭),ivar就会nil再次出现,因此我们回到了初始状态。

在不将弱引用归零的情况下,我们还必须:

  • 在中将其myPopover = nil关闭时设置shouldPerformSegueWithIdentifier:,并且
  • 将我们自己设置为popover控制器的委托,以便进行捕获popoverControllerDidDismissPopover:并在myPopover = nil此处进行设置(因此我们会在自动关闭popover时捕获)。

3
向@wcochran提出支持以帮助您解决此问题。
rickster 2012年

1
谢谢。封装好!(实际上,Apple的主要ObjC语言文档的最新版本将实现ivars作为默认值。)
rickster 2012年

2
是的,这里的__weakivar和weak属性是等效的。在类的内部使用属性还是使用ivar仍然是很多争论的话题。当我怀疑自定义访问器和KVO不再需要时,我倾向于坚持使用ivar,但是“万物属性”策略也有其优点。
rickster 2012年

1
在iOS6版本中,第二次点击如何关闭弹出窗口?不是吗?
mahboudz 2012年

1
解决方案缺少实际消除弹出窗口的位置:[myPopover dismissPopoverAnimated:YES]
苏格兰

13

我在这里找到了解决方案https://stackoverflow.com/a/7938513/665396 在第一个prepareForSegue:sender中:在ivar / property中存储指向UIPopoverController的指针,并使用该指针在随后的调用中消除弹出窗口。

...
@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;
...
}

1
谢谢你 我所做的是将储存在popoverController中,destinationViewController以便以后在我的自定义委托进行回调时可以轻松访问它。
Besi 2012年

2

我为此使用了定制的segue。

1个

创建要在情节提要中使用的自定义segue:

@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

引用由segue分配,后者创建UIPopoverController-消除弹出窗口时

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

问候,彼得


2

我解决了它创建一个自定义 ixPopoverBarButtonItem,它可以触发搜索或消除显示的弹出窗口。

我的工作:我切换按钮的操作和目标,因此它会触发搜索或处理当前显示的弹出窗口。

我花了很多时间来寻找这个解决方案,我不想因为切换动作而获得赞誉。将代码放在自定义按钮中是我尽量减少样例代码在我看来的方法。

在情节提要中,我将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的新手,所以我不确定我是否在这里泄漏。请告诉我我是否...


1
太棒了!那是一个很好的方法,并且效果很好。ARC为您执行大多数内存管理,因此您无需使用发布,保留等功能。这是一篇有关ARC的精彩
Sam Spencer

2

我已解决此问题,而无需保留的副本UIPopoverController。只需处理情节提要中的所有内容(工具栏,BarButtons等),然后

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

这是所有代码:

ViewController.h

@interface ViewController : UIViewController <UIPopoverControllerDelegate>
@end

ViewController.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

1

我接受了rickster的回答,并将其打包到一个从UIViewController派生的类中。此解决方案确实需要满足以下条件:

  • 具有ARC的iOS 6(或更高版本)
  • 从该类派生您的视图控制器
  • 如果要覆盖这些方法,请确保调用prepareForSegue:sender和shouldPerformSegueWithIdentifier:sender的“超级”版本
  • 使用命名的popover 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上找到

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.