【问题标题】:How to Dismiss a Storyboard Popover如何关闭情节提要弹出框
【发布时间】:2012-01-07 09:42:22
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 你是如何创建转场的? segue 的源端是按钮还是视图控制器?您是否为 segue 设置了任何直通?
  • @rob 我通过 Interface builder 创建了 Segue。我会选择按钮并将 Popover Segue 拖到我想要的主视图中。上图显示了这一点。我不确定您评论中最后两个问题的意思。
  • 我使用“单一视图应用程序”模板和情节提要创建了一个新项目。我将一个按钮拖到模板的视图中并拖出第二个视图控制器。我从按钮控制拖到第二个 VC 并选择了 Popover。当我运行它时,我可以触摸按钮使弹出框出现,然后当我触摸弹出框之外的任何地方(包括按钮上)时,弹出框消失。你做了什么不同的事情?
  • 我同意上述评论。再次点击按钮会关闭弹出框。
  • @robmayoff 你说得对,它可以与 UIButton 一起正常工作,但可以尝试使用工具栏中的 UIBarButton 项执行相同的步骤。起初我也没有看到,所以我编辑了 RazorSharp 的问题以使其更清楚。

标签: ios objective-c uipopovercontroller uibarbuttonitem uistoryboard


【解决方案1】:

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

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


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

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

1:从按钮开始

从 iOS 5 开始,您无法使用来自 UIBarButtonItem 的 segue 进行此操作,但您可以在 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: 中以编程方式,还是由用户在弹出框之外的其他地方自动点击——ivar 都会转到nil再次,所以我们回到了我们的初始状态。

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

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

【讨论】:

  • 向@wcochran 提出建议以帮助解决这个问题。
  • 谢谢。封装好! (事实上​​,Apple 最新版本的主 ObjC language documentation 将实现 ivars 视为默认值。)
  • 是的,__weak ivar 和 weak 属性在这里是等价的。是否将属性或 ivar 用于类内部的东西仍然是一个备受争议的话题。当我怀疑永远不需要自定义访问器和 KVO 时,我倾向于坚持使用 ivar,但“所有事物的属性”策略也有其优点。
  • 在iOS6版本中,第二次点击如何关闭弹窗?没有,是吗?
  • 解决方案缺少弹出框实际关闭的位:[myPopover dismissPopoverAnimated:YES]
【解决方案2】:

我在这里找到了解决方案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;
...
}

【讨论】:

  • 谢谢。我所做的是将popoverController 存储在destinationViewController 中,以便稍后在我的自定义委托回调时轻松访问它。
【解决方案3】:

我为此使用了自定义 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 的源/输入的视图控制器中,例如从行动开始:

-(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;
    }    
}

问候, 彼得

【讨论】:

    【解决方案4】:

    我解决了这个问题,创建了一个自定义 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
    

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

    【讨论】:

    • 太棒了!这是一个很好的方法,而且效果很好。 ARC为你做了大部分的内存管理,所以你永远不需要使用release、retain等。这是一篇关于ARC的神文:longweekendmobile.com/2011/09/07/…
    【解决方案5】:

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

    • 通过布尔值处理弹出框的可见性,
    • 确保有委托,并将其设置为 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
    

    【讨论】:

      【解决方案6】:

      我把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
      

      Source available on GitHub

      【讨论】:

        猜你喜欢
        • 2016-03-19
        • 2016-05-03
        • 2019-09-11
        • 2016-01-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-04
        • 1970-01-01
        相关资源
        最近更新 更多