如何检查一个视图控制器是否可以执行转场操作

37

这可能是一个非常简单的问题,但当我搜索时没有得到任何结果,所以在这里提出来...

我想找到一种方法,在调用performSegueWithIdentifier:方法之前检查某个视图控制器是否能够使用标识符XYZ执行segue。

类似于以下内容:

if ([self canPerformSegueWithIdentifier:@"SegueID"])
    [self performSegueWithIdentifier:@"SegueID"];

可能吗?


正是我此刻正在寻找答案的问题... - Dan F
1
嗨,丹,我最终使用了@try @catch @finally。它运行良好。 - Rog
我也这么做了,只是真的希望有一种检查的方法。通常,我会尽量避免在正常运行时可能抛出异常的情况。 - Dan F
2
好奇为什么您事先不知道viewController是否能够处理segue。是否存在代码设计问题等? - spring
@TOMATO 在我的情况下,我想要自动配置。如果一个东西有一个关联的segue,我可以配置它的UI来显示它(具体来说,是UITableViewCell实例,并希望配置它们的附件)。有很多其他方法可以实现相同的目标(我正在使用其中一种),但我能想到的方法会导致重复,而我想避免这种情况。 - Benjohn
9个回答

24

为了检查是否存在segue,我只是用try-and-catch块包围了调用。请参见下面的代码示例:

@try {
    [self performSegueWithIdentifier:[dictionary valueForKey:@"segue"] sender:self];
}
@catch (NSException *exception) {
    NSLog(@"Segue not found: %@", exception);
}
希望这可以帮助你。

Hope this helps.

返回结果:

希望这可以帮助你。


1
这个解决方案非常好。请注意,如果您有异常断点,它仍然会中断。但是您可以继续而不崩溃。 - VaporwareWolf
2
在ARC下,由于它不会在异常后清理,因此可能会出现内存泄漏。 - pronebird
1
不要使用这个。正如Andy所说,它会泄漏视图控制器发送performSegueWithIdentifier:sender:消息以及所有子视图控制器(因为UIKit保留视图控制器但从未有机会释放它,由于异常)。 - Guillaume Algis

15
- (BOOL)canPerformSegueWithIdentifier:(NSString *)identifier
{
    NSArray *segueTemplates = [self valueForKey:@"storyboardSegueTemplates"];
    NSArray *filteredArray = [segueTemplates filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"identifier = %@", identifier]];
    return filteredArray.count>0;
}

3
谢谢,这很有用(尽管这是未记录的行为)。 - Rog
我很遗憾只能给一个赞。Evgeny,你对谓词的运用非常棒。 - Alex Zavatone
根据https://dev59.com/oZPfa4cB1Zd3GeqPD3-X#35060917的答案,使用这种未经记录的行为可能会导致您的应用被应用商店拒绝。 - Andy Mortimer
很好,顺便提一下,如果它是自定义segue类,你也可以检查segueClassName。 - malhal

15

这篇文章已更新至Swift 4。


以下是一种更正确的Swift方式来检查segue是否存在:

extension UIViewController {
func canPerformSegue(withIdentifier id: String) -> Bool {
        guard let segues = self.value(forKey: "storyboardSegueTemplates") as? [NSObject] else { return false }
        return segues.first { $0.value(forKey: "identifier") as? String == id } != nil
    }

    /// Performs segue with passed identifier, if self can perform it.
    func performSegueIfPossible(id: String?, sender: AnyObject? = nil) {
        guard let id = id, canPerformSegue(withIdentifier: id) else { return }
        self.performSegue(withIdentifier: id, sender: sender)
    }
}

// 1
if canPerformSegue("test") {
    performSegueIfPossible(id: "test") // or with sender: , sender: ...)
}

// 2
performSegueIfPossible(id: "test") // or with sender: , sender: ...)

很好的答案,我关心的是self.valueForKey("storyboardSegueTemplates"),"storyboardSegueTemplates"将是一个私有属性,因为它没有在任何地方公开/记录。这段代码是否通过应用商店验证?有人在你的生产应用程序中使用过吗? - Naresh Reddy M

7

根据文档所述:

应用程序通常不需要直接触发segue。 相反,您可以在与视图控制器相关联的Interface Builder中配置一个对象, 例如嵌入在其视图层次结构中的控件,以触发segue。但是,您可以调用此方法来以编程方式触发segue, 也许作为对无法在故事板资源文件中指定的某些操作的响应。例如,您可能会从自定义操作处理程序中调用它, 该处理程序用于处理摇晃或加速度计事件。

接收此消息的视图控制器必须已从故事板加载。 如果视图控制器没有关联的故事板,可能是因为您自己分配和初始化了它, 则此方法会引发异常。

话虽如此,当您触发segue时,通常是因为假定UIViewController将能够使用特定的segue标识符进行响应。我也同意Dan F的看法,您应该尝试避免可能会引发异常的情况。 这是您无法执行此类操作的原因:

if ([self canPerformSegueWithIdentifier:@"SegueID"])
    [self performSegueWithIdentifier:@"SegueID"];

我猜测:
1. respondsToSelector: 只检查是否能够在运行时处理该消息。在这种情况下,您可以,因为类UIViewController 能够响应performSegueWithIdentifier:sender:。要实际检查方法是否能够处理带有某些参数的消息,我想这是不可能的,因为为了确定是否可能,它必须实际运行它,并且在这样做时会引发NSInvalidArgumentException
2. 要创建您建议的内容,实际上会有助于接收与UIViewController相关联的segue id列表。从UIViewController文档中,我无法找到任何看起来像那样的内容。
就目前而言,我猜您最好继续使用@try @catch @finally

4
您可以重写 -(BOOL)shouldPerformSegueWithIdentifier:sender: 方法,在其中执行您的逻辑。
- (BOOL) shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
    if ([identifier isEqualToString:@"someSegue"]) {
        if (!canIPerformSegue) {
            return NO;
        }
    }
    return YES;    
}

希望这可以帮助到你。

2

参考 CanPerformSegue.swift

import UIKit

extension UIViewController{
    func canPerformSegue(identifier: String) -> Bool {
        guard let identifiers = value(forKey: "storyboardSegueTemplates") as? [NSObject] else {
            return false
        }
        let canPerform = identifiers.contains { (object) -> Bool in
            if let id = object.value(forKey: "_identifier") as? String {
                return id == identifier
            }else{
                return false
            }
        }
        return canPerform
    }
}

1

在调用 performSegue 之前,检查基础 UIViewController 上的本地 storyboard 属性将会很有用(例如屏幕是来自 StoryBoard 还是手动实例)

extension UIViewController {

func performSegueWithValidate(withIdentifier identifier: String, sender: Any?) {
    if storyboard != nil {
        performSegue(withIdentifier: identifier, sender: sender)
    }
}

在此输入图片描述


1

以下是 Evgeny Mikhaylov 的答案的 Swift 版本,对我很有用:

我在两个视图中重复使用一个控制器。这帮助我重复使用代码。

if(canPerformSegueWithIdentifier("segueFoo")) {
  self.performSegueWithIdentifier("segueFoo", sender: nil)
}
else {
  self.performSegueWithIdentifier("segueBar", sender: nil)
}


func canPerformSegueWithIdentifier(identifier: NSString) -> Bool {
    let templates:NSArray = self.valueForKey("storyboardSegueTemplates") as! NSArray
    let predicate:NSPredicate = NSPredicate(format: "identifier=%@", identifier)

    let filteredtemplates = templates.filteredArrayUsingPredicate(predicate)
    return (filteredtemplates.count>0)
}

我相信这个方法的名称在iOS中是保留的,因为我使用这种方法时遇到了意外的行为。最好将其重命名,例如更改为canRunSegueWithIdentifier - Andrey Gordeev

-1

使用标准函数无法检查,您可以做的是子类化UIStoryboardSegue并将信息存储在传递给构造函数的source视图控制器中。在界面生成器中选择“自定义”作为segue类型,然后输入segue的类名,这样每个实例化的segue都会调用您的构造函数,如果存在存储的数据,则可以查询它。

您还必须重写perform方法,以调用[source presentModalViewController:destination animated:YES][source pushViewController:destination animated:YES],具体取决于您想要的过渡类型。


以编程方式调用present、push和pop是非常危险的,因为segue随时可以从模态转换为推送场景。当你弹出一个模态时,它会崩溃。最好确保命名的segue存在或使用try/catch,但我认为这仍然很粗糙,因为它并不是真正的异常。应该有一种方法来检查是否定义了segue标识符。这似乎是故意省略的。 - Brennan

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