Impedir segue no método prepareForSegue?

249

É possível cancelar um segue no prepareForSegue:método?

Desejo executar alguma verificação antes do segue e, se a condição não for verdadeira (nesse caso, se houver algum UITextFieldvazio), exibir uma mensagem de erro em vez de executar o segue.

Shmidt
fonte

Respostas:

485

É possível no iOS 6 e posterior: você precisa implementar o método

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

No seu controlador de exibição. Você faz sua validação lá e, se estiver tudo bem, return YES;se não estiver, return NO;e o prepareForSegue não será chamado.

Observe que esse método não é chamado automaticamente quando o acionamento segue programaticamente. Se você precisar executar a verificação, precisará chamar shouldPerformSegueWithIdentifier para determinar se deve executar segue.

Abraão
fonte
106
Para sua informação, se o seguinte for acionado programaticamente chamando [self performSegueWithIdentifier: @ "segueIdentifier" sender: nil]; shouldPerformSegueWithIdentifier nunca será chamado.
O cara
3
@ Thedude obrigado por apontar isso. Estava rastreando um problema e não estava atingindo meu ponto de interrupção. Para quem estiver curioso, basta chamar esse método envolto em uma instrução if para obter o mesmo resultado.
jpittman
1
@jpittman, por favor, explique o que você quer dizer com uma declaração if?
Boda Taljo 21/07
7
@AubadaTaljo: (desculpas para a formatação) if ([self shouldPerformSegueWithIdentifier:@"segueIdentifier" sender:nil]) { [self performSegueWithIdentifier:@"segueIdentifier" sender:nil]; }
TimMedcalf
Tentei isso no iOS 11,3 SDK de um segue storyboard e "shouldPerformSegueWithIdentifier" que são chamados automaticamente
Menno
52

Nota: a resposta aceita é a melhor abordagem se você puder segmentar o iOS 6. Para segmentar o iOS 5, esta resposta será adequada.

Não creio que seja possível cancelar uma sequência prepareForSegue. Eu sugeriria mover sua lógica ao ponto em que a performSeguemensagem foi enviada pela primeira vez.

Se você estiver usando o Interface Builder para conectar um segue diretamente a um controle (por exemplo, vinculando um segue diretamente a um UIButton), poderá fazer isso com um pouco de refatoração. Conecte as segue ao controlador de exibição em vez de um controle específico (exclua o link antigo segue e arraste com o controle do próprio controlador de exibição para o controlador de exibição de destino). Em seguida, crie um IBActionno seu controlador de exibição e conecte o controle ao IBAction. Em seguida, você pode fazer sua lógica (verifique se há TextField vazio) no IBAction que você acabou de criar e decidir lá se deve ou não performSegueWithIdentifierprogramaticamente.

Mike Mertsock
fonte
Se a sequência for para um controlador de popover, você não deseja dar um segundo toque no botão para criar outro controlador de popover; a coisa correta a fazer neste caso é descartar a popover. Sua resposta permite esse comportamento adequado. Se você conectá-lo diretamente a partir do botão no storyboard, não vejo como obter o comportamento adequado.
Wcochran
1
Após várias horas frustrantes de tentar fazer com que vários popovers baseados em seguidas tocassem bem juntos, desisti e me livrei dos seques de popover em favor desta solução. Na verdade, ele usa menos código.
mpemburn
Isso não derrotaria o propósito de ter seguidores?
Cristik
O fato de vincular o ViewController ao ViewController resolveu meu problema. Obrigado! Esta é a melhor solução
Dr TJ
19

Swift 3 : func shouldPerformSegue (com identificador de identificador: String, sender: Any?) -> Bool

Valor de retorno true se o segue deve ser executado ou false se deve ser ignorado.

Exemplo :

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
}
OrdoDei
fonte
12

Como alternativa, é um comportamento um pouco ruim oferecer um botão que o usuário não deve pressionar. Você pode deixar os seguimentos conectados como suportes, mas comece com o botão desativado. Em seguida, conecte "editChanged" do UITextField a um evento no controle de exibição ala

- (IBAction)nameChanged:(id)sender {
    UITextField *text = (UITextField*)sender;
    [nextButton setEnabled:(text.text.length != 0)];
}
Caulim Fogo
fonte
"Alternativamente, é um mau comportamento oferecer um botão que o usuário não deve pressionar". Eu discordo disso - isso é parcialmente verdade, mas realmente depende do contexto. Também é um mau comportamento não orientar o usuário - por exemplo, para que ele toque em um botão e o sistema explique o que precisa ser feito primeiro. Com deficiência ou invisíveis botão os usuários vão quer se perder ou suporte chamada ...
csmith
11

É fácil no rápido.

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

    return true
}
Zumry Mohamed
fonte
3
Hã? Você pode elaborar mais sobre esta resposta? Respostas só de código não são muito úteis para novos leitores ...
Cristik
9

Como Abraham disse, verifique válido ou não na seguinte função.

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

E, o performSegueWithIdentifier:sender:chamado pela programação pode ser bloqueado substituindo o método a seguir. Por padrão, não está verificando se é válido ou não -shouldPerformSegueWithIdentifier:sender:, podemos fazê-lo manualmente.

- (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];
}
AechoLiu
fonte
essa parte é [super performSegueWithIdentifier:identifier sender:sender];realmente verdadeira?
Ben Wheeler
@BenWheeler Você pode experimentá-lo. Se você substituir o performSegueWithIdentifier:sender:método, e não chamá-lo de supermétodo.
precisa saber é o seguinte
5

Deve executar o segmento para o registro de logon

-(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);

}
Swappyee
fonte
4

Semelhante à resposta de Kaolin é deixar o seque conectado ao controle, mas validar o controle com base nas condições da exibição. Se você estiver acionando a interação da célula da tabela, também precisará definir a propriedade userInteractionEnabled, além de desativar o material na célula.

Por exemplo, eu tenho um formulário em uma exibição de tabela agrupada. Uma das células leva a outro tableView que atua como um selecionador. Sempre que um controle é alterado na exibição principal, eu chamo esse método

-(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;
    }

}
James Moore
fonte
4

Resposta rápida 4:

A seguir está a implementação do Swift 4 para cancelar 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
}
Pankaj Kulkarni
fonte
2

A outra maneira é substituir o método de tableView por willSelectRowAt e retornar nil se você não quiser mostrar o segue. showDetails()- é algum bool. Na maioria dos casos, deve ser implementado no modelo de dados representado na célula com indexPath.

 func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
        if showDetails() {
                return indexPath            
        }
        return nil
    }
Bogdan Ustyak
fonte