如何解雇故事板Popover

时间:2011-11-27 16:57:10

标签: ios objective-c uipopovercontroller uibarbuttonitem uistoryboard

我使用Xcode Storyboards从UIBarButtonItem创建了一个popover(所以没有代码),如下所示:

Xcode 5.0 Connections Inspector with Popover

呈现弹出窗口工作得很好。但是,当我点击显示它的UIBarButtonItem时,我无法将消失

按下按钮(第一次)时会出现弹出窗口。当再次按下该按钮(第二次)时,它上面会出现相同的弹出窗口,所以现在我有两个弹出窗口(如果我继续按下按钮,则会有更多弹出窗口)。根据{{​​3}}我需要让弹出窗口出现在第一次点击并消失在第二次:

  

确保一次只能在屏幕上显示一个弹出窗口。您不应同时显示多个弹出窗口(或设计为外观和行为的自定义视图)。特别是,您应该避免同时显示级联或层次结构的弹出窗口,其中一个弹出窗口从另一个弹出窗口出现。

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

6 个答案:

答案 0 :(得分:114)

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

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


如果您存储对segue的弹出控制器的引用,在重复调用prepareForSegue:sender:之前将其设置为新值之前将其解除,您可以避免重复按下按钮时获得多个堆栈弹出窗口的问题 - 你仍然不能像HIG推荐的那样使用按钮来解除弹出窗口(如Apple的应用程序中所示)

您可以利用ARC归零弱引用来获得简单的解决方案:

1:从按钮

发出警告

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

2:在-shouldPerformSegue...

中使用对popover的引用
@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转到{再次{1}},所以我们回到了初始状态。

如果没有归零弱引用,我们还必须:

  • nil
  • 中解除时设置myPopover = nil
  • 将我们自己设置为弹出控制器的委托,以便捕获shouldPerformSegueWithIdentifier:并在那里设置popoverControllerDidDismissPopover:(因此我们会在弹出窗口被自动关闭时捕获)。

答案 1 :(得分:13)

我在这里找到了解决方案https://stackoverflow.com/a/7938513/665396 在第一个prepareForSegue:sender:存储在ivar / property中指向UIPopoverController的指针和用户指向在后续调用中解除popover的指针。

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

答案 2 :(得分:2)

我已经使用了自定义segue。

1

创建要在Storyboard中使用的自定义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的源/输入的视图控制器中,例如通过行动启动赛道:

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

3

引用由segue分配,它创建UIPopoverController - 当解除popover

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

的问候, 彼得

答案 3 :(得分:2)

我解决了它创建一个自定义ixPopoverBarButtonItem,它会触发segue或解除显示的弹出窗口。

我的所作所为:我切换动作&按钮的目标,所以它要么触发segue,要么处理当前显示的弹出窗口。

我花了很多谷歌搜索这个解决方案,我不想拿转换为切换动作的想法。将代码放入自定义按钮是我将样板代码保持在我的视图中的最小方法。

在故事板中,我将BarButtonItem的类定义为我的自定义类:

custom bar button

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

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

顺便说一下......因为我有多个按钮触发弹出窗口,我仍然需要保留当前显示的弹出窗口的引用并在我使新的弹出窗口可见时将其解除,但这不是你的问题... < / p>

以下是我实现自定义UIBarButtonItem的方法:

...界面:

@interface ixPopoverBarButtonItem : UIBarButtonItem

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

@end

...和impl:

#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的新手,所以我不确定我是否在这里泄漏。请告诉我,如果我......

答案 4 :(得分:2)

我已经解决了这个问题,无需保留UIPopoverController的副本。只需处理故事板中的所有内容(工具栏,BarButtons等)和

  • 通过布尔值
  • 处理弹出窗口的可见性
  • 确保有委托,并设置为自我

以下是所有代码:

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

答案 5 :(得分:1)

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

  • 带有ARC的iOS 6(或更高版本)
  • 从此课程中导出视图控制器
  • 确保调用prepareForSegue的“超级”版本:sender和shouldPerformSegueWithIdentifier:sender如果要覆盖这些方法
  • 使用命名的popover segue

关于这一点的好处是你不必做任何“特殊”编码来支持正确处理Popovers。

<强>接口

@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

Source available on GitHub