[英]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
(所以没有代码),如下所示:
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归零弱引用来获得简单的解决方案:
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:
检查弹出窗口后。)
-shouldPerformSegue...
-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
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: 在没有归零弱引用的情况下,我们还必须:
myPopover = nil
when dismissing it in shouldPerformSegueWithIdentifier:
, and shouldPerformSegueWithIdentifier:
解除它时设置myPopover = nil
shouldPerformSegueWithIdentifier:
和 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等),以及
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。
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
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];
}
}
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:
此解决方案需要以下内容:
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
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.