UIStoryboardPopoverSegue在按钮触摸时打开多个窗口

15
我正在使用UIStoryboardPopoverSegue在iOS 5 iPad应用程序中呈现弹出窗口。这个Segue非常有效,但似乎包含按钮的工具栏是弹出窗口控制器的穿透视图,所以如果你一直按下按钮,就会出现更多的弹出窗口。由于我没有自己创建和跟踪UIPopoverController(因为Storyboard正在执行它),所以当再次触摸按钮时无法解散它。有其他人遇到过这个问题吗?我向苹果提出了一个错误报告,但他们没有回应。
编辑:我使用下面的答案解决了这个问题。这是我最终使用的代码。currentPopover是我的视图控制器类中的一个__weak实例变量,因此当控制器完成时,它将自动降为nil。
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    if([segue isKindOfClass:[UIStoryboardPopoverSegue class]]){
        // Dismiss current popover, set new popover
        [currentPopover dismissPopoverAnimated:YES];
        currentPopover = [(UIStoryboardPopoverSegue *)segue popoverController];
    }
}

感谢您的编辑,这是一个优雅的问题解决方案! - shapecatcher
7个回答

7

你的解决方案存在一些视觉问题,Cory。

有两个选项可以考虑-简单地删除或更改呈现弹出窗口的按钮的操作。

选项1,保留指向按钮动作的指针,并在呈现弹出窗口后将其设置为nil。在关闭弹出窗口时重置为原始操作。

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{

    action = [sender action];
    [sender setAction:nil];

    self.currentPopover = [(UIStoryboardPopoverSegue *)segue popoverController];
    self.currentPopover.delegate = self;
}

-(BOOL)popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController
{
    [self.navigationItem.rightBarButtonItem setAction:action];

    return YES;
}

这样,弹出框只会出现一次,并且将按预期被关闭。

第二个选项是更改按钮的功能,使得当弹出框可见时,点击按钮会导致弹出框关闭。

    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {

        action = [sender action];
        target = [sender target];

        [sender setTarget:self];
        [sender setAction:@selector(dismiss:)];

        self.currentPopover = [(UIStoryboardPopoverSegue *)segue popoverController];
        self.currentPopover.delegate = self;
    }

-(void)dismiss:(id)sender
{
    [self.navigationItem.rightBarButtonItem setAction:action];
    [self.navigationItem.rightBarButtonItem setTarget:target];
    ////or
//  [sender setAction:action];
//  [sender setTarget:target];
    [self.currentPopover dismissPopoverAnimated:YES];
}


    -(BOOL)popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController
    {
        [self.navigationItem.rightBarButtonItem setAction:action];
        [self.navigationItem.rightBarButtonItem setTarget:target];

        return YES;
    }

Robert的解决方案对我来说完全有效。我认为这就是苹果在UISplitViewController中实现此技术的方式。 只是一个附加项,您需要添加两个ivars: SEL action; id targer;干杯! - Raunak
太好了!我唯一要补充的是另一个ivar:id myPopButton,并在prepareForSegue中设置它:myPopButton = sender; 这样,您可以引用多个按钮(当从工具栏调用时)。在“dismiss”和“popoverControllerShouldDismissPopover”方法中,调用:[myPopButton setAction:action]; 和[myPopButton setTarget:target]; 经过测试,这很有效(iOS SDK 6.0)。 - mpemburn

3

只需通过 IBAction 连接一个 UIBarButtonItem。使用在界面构建器中设置的标识符:

-(IBAction)barButtonItemPressed:(id)sender {
    if (currentPopoverController && currentPopoverController.popoverVisible) {
        [currentPopoverController dismissPopoverAnimated:YES];
        currentPopoverController = nil;
    } else {
        [self performSegueWithIdentifier:@"aSegueIdentifier" sender:sender];
    }
}

从序列中获取新的 UIPopoverController 的引用:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([[segue identifier] isEqualToString:@"aSegueIdentifier"])
        currentPopoverController = [(UIStoryboardPopoverSegue *)segue popoverController];
}

currentPopoverController是一个实例变量,定义在头文件中:

UIPopoverController *currentPopoverController;

重要提示:seque的anchor属性必须设置为相应的UIBarButtonItem!

除非您还将currentPopoverController的委托设置为nil并在didDismiss中清除对它的引用,否则您仍将在此处遇到多个弹出窗口问题。我首选的解决方法与此大致相同,但我将currentPopoverController设置为弱引用--这样无论如何弹出窗口被解除,它都会自动获取nil值。 - rickster

2

您需要在prepareForSegue类方法中存储作为UIStoryboardPopoverSegue类的一部分传递的popoverController属性的引用。

要访问它,请像这样重写调用视图控制器中的方法:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    // The Storyboard Segue is named popover in this case:
    if ([segue.identifier compare:@"popover"] == NSOrderedSame) {
        // segue.popoverController is only present in popover segue's
        // self.seguePopoverController is a UIPopoverController * property.
        self.seguePopoverController = segue.popoverController;
    }
}

然后您可以按照通常的方式将其关闭。

虽然我希望苹果能提供更自动化的解决方案,但这个方法确实可行。我已经在原帖中添加了我使用的代码,供有兴趣的人参考。 - Cory Imdieke
太棒了!这种方法在编译为iOS 7的代码上崩溃,在运行iOS 8.2的iPad上更是如此。你必须使用解开segue来关闭弹出窗口。感谢苹果。 - Duck

2

这个解决方案可能会有一些视觉问题,但在我简单的情况下没有。在我的情况下,弹出窗口只是显示一些帮助信息。我编写了以下代码(使用ARC),当再次按下按钮栏按钮时,将关闭弹出窗口视图控制器(包括原始和新创建的)。

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{   
    if( [segue isKindOfClass:[UIStoryboardPopoverSegue class]] )
    {
        UIStoryboardPopoverSegue *popoverSegue      = (id)segue;
        UIPopoverController      *popoverController = popoverSegue.popoverController;

        if( m_popoverController.popoverVisible )
        {
            [m_popoverController dismissPopoverAnimated:NO];
            dispatch_async( dispatch_get_main_queue(), ^{
                [popoverController dismissPopoverAnimated:YES];
            });
            m_popoverController = nil;
        }
        else
            m_popoverController = popoverController;        
    }    
}

我还在dealloc中添加了一些清理工作

- (void)dealloc
{
    if( m_popoverController.popoverVisible )
        [m_popoverController dismissPopoverAnimated:YES];
}

在您的类中需要一个成员变量

UIPopoverController *m_popoverController;

2

我更喜欢使用静态弱变量,将所有内容放在一个位置上:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender  
{
    if ([[segue identifier] isEqualToString:@"showSomething"]) {
        static __weak UIPopoverController* currentPopover = nil;
        [currentPopover dismissPopoverAnimated:NO];
        currentPopover = [(UIStoryboardPopoverSegue *)segue popoverController];
        // ...
    }
}

没有必要添加一个单独的额外变量(你什么时候会有多个视图控制器实例?),这样你可以为每个if()块添加一个额外的变量。


1

2013年6月14日

感谢编辑问题。与其关闭并重新创建视图控制器(这会影响性能和电池寿命,并在关闭和重新创建视图控制器时产生闪光),为什么不防止第二个弹出式窗口的弹出呢?

//place in view controller (tested iOS6+, iPad, iPhone)
__weak UIPopoverController *popover;
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if([segue isKindOfClass:[UIStoryboardPopoverSegue class]] 
        && [segue.identifier isEqualToString:@"mySegue"]) //remember to change "mySegue" 
            popover = [(UIStoryboardPopoverSegue *)segue popoverController];
}
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
    if ([identifier isEqualToString:@"mySegue"]) //remember to change "mySegue"
        return !popover;
    else
        return YES;
}
added checks to: https://dev59.com/amsy5IYBdhLWcg3w1xeU#10238581 

1
我喜欢它。你可以将我的原始帖子编辑中的代码与你的shouldPerform代码结合起来,这样会比我单独编辑的代码更美观一些。 - Cory Imdieke

0

这也很好。

@interface ViewController : UIViewController <UIPopoverControllerDelegate> {
    UIPopoverController * seguePopoverController;
}

@property (strong) UIPopoverController * seguePopoverController;

@end

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender  
{
    if (self.seguePopoverController) {
        [self.seguePopoverController dismissPopoverAnimated:NO];
        self.seguePopoverController = nil;
    }

    // The Storyboard Segue is named popover in this case:
    if ([[segue identifier] isEqualToString:@"popover"]) {

        UIStoryboardPopoverSegue* popSegue = (UIStoryboardPopoverSegue*)segue;
        UIPopoverController *thePopoverController = [popSegue popoverController];
        thePopoverController.delegate = self;
        self.seguePopoverController = thePopoverController;

    }
}

- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController
{
    self.seguePopoverController = nil;
}

如果您使用我在问题中添加的代码,弱引用变量将自动降为nil,这将消除大量代码的需要。如果您添加了另一个弹出式转场并忘记将其命名为“popover”,则顶部的代码也将起作用。 - Cory Imdieke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接