我使用Xcode Storyboards从UIBarButtonItem
创建了一个popover(所以没有代码),如下所示:
呈现弹出窗口工作得很好。但是,当我点击显示它的UIBarButtonItem
时,我无法将消失。
按下按钮(第一次)时会出现弹出窗口。当再次按下该按钮(第二次)时,它上面会出现相同的弹出窗口,所以现在我有两个弹出窗口(如果我继续按下按钮,则会有更多弹出窗口)。根据{{3}}我需要让弹出窗口出现在第一次点击并消失在第二次:
确保一次只能在屏幕上显示一个弹出窗口。您不应同时显示多个弹出窗口(或设计为外观和行为的自定义视图)。特别是,您应该避免同时显示级联或层次结构的弹出窗口,其中一个弹出窗口从另一个弹出窗口出现。
当用户第二次点击UIBarButtonItem
时,如何解除弹出窗口?
答案 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归零弱引用来获得简单的解决方案:
从iOS 5开始,您无法使用来自UIBarButtonItem
的segue进行此操作,但您可以在iOS 6及更高版本中使用。 (在iOS 5上,您必须从视图控制器本身中删除,然后在检查弹出窗口后按钮的动作调用performSegueWithIdentifier:
。)
-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
这里使用归零弱引用的好处是,一旦弹出控制器被解除 - 无论是以编程方式在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。
创建要在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
在作为segue的源/输入的视图控制器中,例如通过行动启动赛道:
-(void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
if(nil == self.recentSearchesPopoverController)
{
NSString *identifier = NSStringFromClass([CustomPopoverSegue class]);
[self performSegueWithIdentifier:identifier sender:self];
}
}
引用由segue分配,它创建UIPopoverController - 当解除popover
时-(void)searchBarTextDidEndEditing:(UISearchBar *)searchBar
{
if(self.recentSearchesPopoverController)
{
[self.recentSearchesPopoverController dismissPopoverAnimated:YES];
self.recentSearchesPopoverController = nil;
}
}
的问候, 彼得
答案 3 :(得分:2)
我解决了它创建一个自定义ixPopoverBarButtonItem
,它会触发segue或解除显示的弹出窗口。
我的所作所为:我切换动作&按钮的目标,所以它要么触发segue,要么处理当前显示的弹出窗口。
我花了很多谷歌搜索这个解决方案,我不想拿转换为切换动作的想法。将代码放入自定义按钮是我将样板代码保持在我的视图中的最小方法。
在故事板中,我将BarButtonItem的类定义为我的自定义类:
然后我将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的类。此解决方案确实需要以下内容:
关于这一点的好处是你不必做任何“特殊”编码来支持正确处理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