在prepareForSegue方法中防止segue的发生?

257

prepareForSegue: 方法中取消 segue 是否可能?

我想在 segue 前执行一些检查,如果条件不满足(在这种情况下,如果某个 UITextField 为空),则显示错误消息而不是执行 segue。

10个回答

496

iOS 6及更高版本支持此功能:

您必须实现该方法

- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender 

在您的视图控制器中,进行验证,如果验证通过,return YES;如果不通过,则return NO;,则不会调用prepareForSegue方法。

请注意,当以编程方式触发segue时,该方法不会自动调用。如果您需要执行检查,则必须调用shouldPerformSegueWithIdentifier方法来确定是否执行segue。


109
如果segue是通过编程调用[self performSegueWithIdentifier:@"segueIdentifier" sender:nil];触发的,那么shouldPerformSegueWithIdentifier将永远不会被调用。 - The dude
3
感谢@Thedude指出这一点。我一直在追踪一个问题,但它没有触发我的断点。对于任何好奇的人,你只需要调用这个方法并将其包装在if语句中即可获得相同的结果。 - jpittman
1
@jpittman,您能否解释一下“包含在if语句中”是什么意思? - user1453351
7
如果([self shouldPerformSegueWithIdentifier:@"segueIdentifier" sender:nil]) { [self performSegueWithIdentifier:@"segueIdentifier" sender:nil]; } - TimMedcalf
在iOS 11.3 SDK中尝试了这个从storyboard segue,并且“shouldPerformSegueWithIdentifier”被自动调用。 - Menno

53

注意:如果你的目标是 iOS 6,接受的答案是最佳方法。对于针对 iOS 5 的情况,这个答案是可行的。

我认为在 prepareForSegue 中无法取消 segue。我建议将逻辑移动到第一次发送 performSegue 消息的地方。

如果你正在使用 Interface Builder 直接将 segue 与控件连接(例如直接将 segue 与一个 UIButton 连接),那么你需要进行一些重构。将 segue 连接到视图控制器而不是特定的控件(删除旧的 segue 连接,然后从视图控制器本身向目标视图控制器控件拖动)。然后在你的视图控制器中创建一个 IBAction,并将控件与 IBAction 连接。然后你可以在刚刚创建的 IBAction 中执行逻辑(检查空白文本字段),并在那里决定是否编程方式执行 performSegueWithIdentifier


如果segue是到一个弹出控制器,你想要第二次点击按钮创建另一个弹出控制器;在这种情况下正确的做法是关闭弹出窗口。你的答案允许这种正确的行为。如果你直接从storyboard中的按钮连接它,我看不到任何获取正确行为的方法。 - wcochran
1
在尝试让多个基于segue的弹出窗口彼此协调良好的几个令人沮丧的小时之后,我放弃了,并采用了这种解决方案来替代弹出窗口。实际上,它使用的代码更少。 - mpemburn
这样做不会违背使用segues的初衷吗? - Cristik
将ViewController与ViewController链接的事实解决了我的问题。谢谢!这是最好的解决方案。 - Dr TJ

21

Swift 3: func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool

如果应该执行segue,则返回值true,如果应该忽略,则返回false

示例

var badParameters:Bool = true

override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
    if badParameters  {
         // your code here, like badParameters  = false, e.t.c
         return false
    }
    return true
}

12

另外,提供一个用户不应该按下的按钮是有些不太好的行为。您可以将segue保留为原样,但首先禁用该按钮。然后将UITextField的“editingChanged”连接到视图控件上的事件。

- (IBAction)nameChanged:(id)sender {
    UITextField *text = (UITextField*)sender;
    [nextButton setEnabled:(text.text.length != 0)];
}

相反地,提供一个用户不应该按的按钮是有些不良行为。我不同意这个观点 - 这部分是正确的,但实际上取决于上下文。同时,不指导用户也是不好的行为 - 例如,他们可以点击一个按钮,然后系统解释首先需要做什么。对于一个禁用或隐藏的按钮,用户将会迷失或寻求支持... - csmith

11

在 Swift 中很容易。

override func shouldPerformSegueWithIdentifier(identifier: String,sender: AnyObject?) -> Bool {

    return true
}

3
嗯?你能详细解释一下这个答案吗?仅有代码的答案对后来的读者来说并不是很有用... - Cristik

9

正如Abraham所说的那样,在以下函数中检查是否有效。

- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(nullable id)sender
{
     // Check this identifier is OK or NOT.
}

编程调用的performSegueWithIdentifier:sender:方法可能会被以下方法覆盖而阻塞。默认情况下,它不通过-shouldPerformSegueWithIdentifier:sender:检查是否有效,我们可以手动实现。

- (void)performSegueWithIdentifier:(NSString *)identifier sender:(id)sender
{
    // Check valid by codes
    if ([self shouldPerformSegueWithIdentifier:identifier sender:sender] == NO) {
        return;
    }

    // If this identifier is OK, call `super` method for `-prepareForSegue:sender:` 
    [super performSegueWithIdentifier:identifier sender:sender];
}

这部分关于 [super performSegueWithIdentifier:identifier sender:sender]; 的内容真的是真实的吗? - Ben Wheeler
@BenWheeler 你可以试试。如果你重写了 performSegueWithIdentifier:sender: 方法,但没有调用它的 super 方法。 - AechoLiu

7

Swift 4答案:

以下是Swift 4的实现,用于取消segue:

override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
    if identifier == "EditProfile" {
        if userNotLoggedIn {
            // Return false to cancel segue with identified Edit Profile
            return false
        }
    }
    return true
}

5

应该为登录注册执行Segue

-(BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
{

    [self getDetails];

    if ([identifier isEqualToString:@"loginSegue"])
    {

        if (([_userNameTxtf.text isEqualToString:_uname])&&([_passWordTxtf.text isEqualToString:_upass]))
        {

            _userNameTxtf.text=@"";
            _passWordTxtf.text=@"";

            return YES;
        }
        else
        {
            UIAlertView *loginAlert = [[UIAlertView alloc] initWithTitle:@"Alert" message:@"Invalid Details" delegate:self cancelButtonTitle:@"Try Again" otherButtonTitles:nil];

            [loginAlert show];

            _userNameTxtf.text=@"";
            _passWordTxtf.text=@"";

            return NO;
        }

    }

    return YES;

}

-(void)getDetails
{
    NSArray *dir=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

    NSString *dbpath=[NSString stringWithFormat:@"%@/userDb.sqlite",[dir lastObject]];

    sqlite3 *db;

    if(sqlite3_open([dbpath UTF8String],&db)!=SQLITE_OK)
    {
        NSLog(@"Fail to open datadbase.....");
        return;
    }

    NSString *query=[NSString stringWithFormat:@"select * from user where userName = \"%@\"",_userNameTxtf.text];

    const char *q=[query UTF8String];

    sqlite3_stmt *mystmt;

    sqlite3_prepare(db, q, -1, &mystmt, NULL);

    while (sqlite3_step(mystmt)==SQLITE_ROW)
    {
        _uname=[NSString stringWithFormat:@"%s",sqlite3_column_text(mystmt, 0)];

        _upass=[NSString stringWithFormat:@"%s",sqlite3_column_text(mystmt, 2)];
    }

    sqlite3_finalize(mystmt);
    sqlite3_close(db);

}

4

和Kaolin的答案类似,可以将序列连接到控件上,但是根据视图中的条件验证控件。如果您在表单单元格交互时触发,还需要设置userInteractionEnabled属性以及禁用单元格中的内容。

例如,我有一个分组表格视图中的表单。其中一个单元格导航到另一个作为选择器的tableView。每当在主视图中更改控件时,我调用该方法。

-(void)validateFilterPicker
{
    if (micSwitch.on)
    {
        filterPickerCell.textLabel.enabled = YES;
        filterPickerCell.detailTextLabel.enabled = YES;
        filterPickerCell.userInteractionEnabled = YES;
        filterPickerCell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }
    else
    {
        filterPickerCell.textLabel.enabled = NO;
        filterPickerCell.detailTextLabel.enabled = NO;
        filterPickerCell.userInteractionEnabled = NO;
        filterPickerCell.accessoryType = UITableViewCellAccessoryNone;
    }

}

2
另一种方式是通过覆盖tableView的方法willSelectRowAt,并在不想显示segue时返回nil。 showDetails()是一个布尔值。在大多数情况下,应该在用indexPath表示的单元格中实现数据模型。
 func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
        if showDetails() {
                return indexPath            
        }
        return nil
    }

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