简体   繁体   English

如何解雇故事板Popover

[英]How to Dismiss a Storyboard Popover

I've created a popover from a UIBarButtonItem using Xcode Storyboards (so there's no code) like this: 我使用Xcode Storyboards从UIBarButtonItem创建了一个UIBarButtonItem (所以没有代码),如下所示:

带有Popover的Xcode 5.0 Connections Inspector

Presenting the popover works just fine. 提出popover工作得很好。 However, I can't get the popover to disappear when I tap the UIBarButtonItem that made it appear. 但是,当我点击使它出现的UIBarButtonItem时,我无法让UIBarButtonItem 消失

When the button is pressed (first time) the popover appears. 按下按钮(第一次)时会出现弹出窗口。 When the button is pressed again (second time) the same popover appears on top of it, so now I have two popovers (or more if I continuer pressing the button). 当再次按下该按钮(第二次)时,它上面会出现相同的弹出窗口,所以现在我有两个弹出窗口(如果我继续按下按钮,则会有更多弹出窗口)。 According to the iOS Human Interface Guidelines I need to make the popover appear on the first tap and disappear on the second: 根据iOS人机界面指南,我需要在第一次点击时显示弹出窗口并在第二次点击时消失:

Ensure that only one popover is visible onscreen at a time. 确保一次只能在屏幕上显示一个弹出窗口。 You should not display more than one popover (or custom view designed to look and behave like a popover) at the same time. 您不应同时显示多个弹出窗口(或设计为外观和行为的自定义视图)。 In particular, you should avoid displaying a cascade or hierarchy of popovers simultaneously, in which one popover emerges from another. 特别是,您应该避免同时显示级联或层次结构的弹出窗口,其中一个弹出窗口从另一个弹出窗口出现。

How can I dismiss the popover when the user taps the UIBarButtonItem for a second time? 当用户第二次点击UIBarButtonItem时,如何解除UIBarButtonItem

EDIT: These problems appear to be fixed as of iOS 7.1 / Xcode 5.1.1. 编辑:从iOS 7.1 / Xcode 5.1.1开始,这些问题似乎已得到修复。 (Possibly earlier, as I haven't been able to test all versions. Definitely after iOS 7.0, since I tested that one.) When you create a popover segue from a UIBarButtonItem , the segue makes sure that tapping the popover again hides the popover rather than showing a duplicate. (可能更早,因为我无法测试所有版本。绝对是在iOS 7.0之后,因为我测试了那个。)当你从UIBarButtonItem创建一个UIBarButtonItem segue时,segue确保再次点击UIBarButtonItem会隐藏popover而不是显示重复。 It works right for the new UIPresentationController -based popover segues that Xcode 6 creates for iOS 8, too. 它适用于Xcode 6为iOS 8创建的基于UIPresentationController的新UIPresentationController segues。

Since my solution may be of historical interest to those still supporting earlier iOS versions, I've left it below. 由于我的解决方案可能对仍然支持早期iOS版本的人有历史意义,我将其留在下面。


If you store a reference to the segue's popover controller, dismissing it before setting it to a new value on repeat invocations of prepareForSegue:sender: , all you avoid is the problem of getting multiple stacking popovers on repeated presses of the button -- you still can't use the button to dismiss the popover as the HIG recommends (and as seen in Apple's apps, etc.) 如果您存储对segue的弹出控制器的引用,在重新调用prepareForSegue:sender:之前将其设置为新值之前将其解除,您要避免的是在重复按下按钮时获得多个堆栈弹出窗口的问题 - 您仍然不能像HIG推荐的那样使用按钮来关闭弹出框(如Apple的应用程序中所见)

You can take advantage of ARC zeroing weak references for a simple solution, though: 但是,您可以利用ARC归零弱引用来获得简单的解决方案:

1: Segue from the button 1:从按钮开始

As of iOS 5, you couldn't make this work with a segue from a UIBarButtonItem , but you can on iOS 6 and later. 从iOS 5开始,你无法使用来自UIBarButtonItem的segue,但你可以在iOS 6及更高版本上使用。 (On iOS 5, you'd have to segue from the view controller itself, then have the button's action call performSegueWithIdentifier: after checking for the popover.) (在iOS 5上,您必须从视图控制器本身中删除,然后按钮的动作调用performSegueWithIdentifier:检查弹出窗口后。)

2: Use a reference to the popover in -shouldPerformSegue... 2:在-shouldPerformSegue...使用对-shouldPerformSegue...的引用-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: There's no step three! 3:没有第三步!

The nice thing about using a zeroing weak reference here is that once the popover controller is dismissed -- whether programmatically in shouldPerformSegueWithIdentifier: , or automatically by the user tapping somewhere else outside the popover -- the ivar goes to nil again, so we're back to our initial state. 在这里使用归零弱引用的shouldPerformSegueWithIdentifier:是,一旦弹出控制器被解散 - 无论是以编程方式在shouldPerformSegueWithIdentifier: ,还是由用户在弹出窗口外的其他位置自动shouldPerformSegueWithIdentifier: - ivar再次变为nil ,所以我们是回到我们的初始状态。

Without zeroing weak references, we'd have to also: 在没有归零弱引用的情况下,我们还必须:

  • set myPopover = nil when dismissing it in shouldPerformSegueWithIdentifier: , and shouldPerformSegueWithIdentifier:解除它时设置myPopover = nil shouldPerformSegueWithIdentifier:
  • set ourself as the popover controller's delegate in order to catch popoverControllerDidDismissPopover: and also set myPopover = nil there (so we catch when the popover is automatically dismissed). popoverControllerDidDismissPopover:设置为myPopover = nil控制器的委托,以便捕获popoverControllerDidDismissPopover:并在那里设置myPopover = nil (所以我们在弹出窗口被自动关闭时捕获)。

I found the solution here https://stackoverflow.com/a/7938513/665396 In first prepareForSegue:sender: store in a ivar/property the pointer to the UIPopoverController and user that pointer to dismiss the popover in the subsequent invocations. 我在这里找到了解决方案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;
...
}

I solved it creating a custom ixPopoverBarButtonItem that either triggers the segue or dismisses the popover being shown. 我解决了它创建一个自定义ixPopoverBarButtonItem ,它触发segue或解除显示的弹出窗口。

What I do: I toggle the action & target of the button, so it either triggers the segue, or disposes the currently showing popover. 我做什么:我切换按钮的动作和目标,因此它要么触发segue,要么处理当前显示的弹出窗口。

It took me a lot of googling for this solution, I don't want to take the credits for the idea of toggling the action. 我花了很多谷歌搜索这个解决方案,我不想拿转换为切换动作的想法。 Putting the code into a custom button was my approach to keep the boilerplate code in my view to a minimum. 将代码放入自定义按钮是我将样板代码保持在我的视图中的最小方法。

In the storyboard, I define the class of the BarButtonItem to my custom class: 在故事板中,我将BarButtonItem的类定义为我的自定义类:

自定义栏按钮

Then I pass the popover created by the segue to my custom button implementation in the prepareForSegue:sender: method: 然后我将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];
    }
}

Btw... since I have more than one buttons triggering popovers, I still have to keep a reference of the currently displayed popover and dismiss it when I make the new one visible, but this was not your question... 顺便说一句......因为我有多个按钮触发弹出窗口,我仍然需要保留当前显示的弹出窗口的引用并在我使新的弹出窗口可见时将其解除,但这不是你的问题......

Here is how I implemented my custom UIBarButtonItem: 以下是我实现自定义UIBarButtonItem的方法:

...interface: ...接口:

@interface ixPopoverBarButtonItem : UIBarButtonItem

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

@end

... and impl: ...并且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: I am new to ARC, so I am not entirely sure if I am leaking here. ps:我是ARC新手,所以我不确定我是否在这里泄漏。 Please tell me if I am... 请告诉我,如果我...

I have solved this problem with no need to keep a copy of a UIPopoverController . 我已经解决了这个问题,无需保留UIPopoverController的副本。 Simply handle everything in storyboard (Toolbar, BarButtons. etc.), and 只需处理故事板中的所有内容(工具栏,BarButtons等),以及

  • handle visibility of the popover by a boolean, 通过布尔值处理弹出窗口的可见性,
  • make sure there is a delegate, and it is set to self 确保有一个委托,并设置为自己

Here is all the code: 这是所有代码:

ViewController.h ViewController.h

@interface ViewController : UIViewController <UIPopoverControllerDelegate>
@end

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

I've used custom segue for this. 我已经使用了自定义segue。

1 1

create custom segue to use in Storyboard: 创建要在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 2

in view controller that is source/input of segue eg start segue with action: 视图控制器是segue的源/输入,例如启动segue with action:

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

3 3

references are assigned by segue which creates UIPopoverController - when dismissing popover 引用由segue分配,它创建UIPopoverController - 在解除popover时

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

regards, Peter 亲爱的,彼得

I took rickster's answer and packaged it into a class derived from UIViewController. 我把rickster的答案打包并打包成一个派生自UIViewController的类。 This solution does require the following: 此解决方案需要以下内容:

  • iOS 6 (or later) with ARC 带有ARC的iOS 6(或更高版本)
  • Derive your view controller from this class 从此类派生您的视图控制器
  • make sure to call the "super" versions of prepareForSegue:sender and shouldPerformSegueWithIdentifier:sender if you are overriding those methods 如果要覆盖这些方法,请务必调用prepareForSegue:sender和shouldPerformSegueWithIdentifier:sender的“超级”版本
  • Use a named popover segue 使用命名的popover segue

The nice thing about this is you don't have to do any "special" coding to support the proper handling of Popovers. 关于这一点的好处是你不必做任何“特殊”编码来支持正确处理Popovers。

Interface : 界面

@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 : 实施

@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 来源于GitHub

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM