Empêcher l'enchaînement dans la méthode prepareForSegue?

249

Est-il possible d'annuler une séquence dans la prepareForSegue:méthode?

Je souhaite effectuer une vérification avant la séquence, et si la condition n'est pas vraie (dans ce cas, si une partie UITextFieldest vide), afficher un message d'erreur au lieu d'effectuer la séquence.

Shmidt
la source

Réponses:

485

C'est possible dans iOS 6 et versions ultérieures: vous devez implémenter la méthode

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

Dans votre contrôleur de vue. Vous faites votre validation là-bas, et si c'est OK alors return YES;si ce n'est pas le cas return NO;et que prepareForSegue n'est pas appelé.

Notez que cette méthode n'est pas appelée automatiquement lors du déclenchement de séquences par programme. Si vous devez effectuer la vérification, vous devez alors appeler shouldPerformSegueWithIdentifier pour déterminer s'il faut effectuer la transition.

Abraham
la source
106
Pour info, si la séquence est déclenchée par programme en appelant [self performSegueWithIdentifier: @ "segueIdentifier" sender: nil]; shouldPerformSegueWithIdentifier ne sera jamais appelé.
Le mec
3
@Thedude merci de l'avoir signalé. Je recherchais un problème et il n'a pas atteint mon point d'arrêt. Pour toute personne curieuse, il vous suffit d'appeler cette méthode enveloppée dans une instruction if pour obtenir le même résultat.
jpittman
1
@jpittman, pourriez-vous expliquer ce que vous entendez par enveloppé dans une instruction if?
Boda Taljo
7
@AubadaTaljo: (excuses pour le formatage) if ([self shouldPerformSegueWithIdentifier:@"segueIdentifier" sender:nil]) { [self performSegueWithIdentifier:@"segueIdentifier" sender:nil]; }
TimMedcalf
J'ai essayé cela dans le SDK iOS 11.3 à partir d'une séquence de storyboard et «shouldPerformSegueWithIdentifier» a été appelé automatiquement
Menno
52

Remarque: la réponse acceptée est la meilleure approche si vous pouvez cibler iOS 6. Pour cibler iOS 5, cette réponse fera l'affaire.

Je ne pense pas qu'il soit possible d'annuler un enchaînement prepareForSegue. Je suggérerais de déplacer votre logique au point que le performSeguemessage est d'abord envoyé.

Si vous utilisez Interface Builder pour câbler un enchaînement directement à un contrôle (par exemple, en liant un enchaînement directement à a UIButton), vous pouvez accomplir cela avec un peu de refactoring. Câblez la séquence au contrôleur de vue au lieu d'un contrôle spécifique (supprimez l'ancien lien de séquence, puis faites glisser le contrôle du contrôleur de vue lui-même vers le contrôleur de vue de destination). Créez ensuite un IBActioncontrôleur dans votre vue et connectez le contrôle à IBAction. Ensuite, vous pouvez faire votre logique (vérifier TextField vide) dans l'IBAction que vous venez de créer, et y décider de le faire ou non par performSegueWithIdentifierprogrammation.

Mike Mertsock
la source
Si la transition se fait vers un contrôleur popover, vous ne voulez pas qu'un deuxième appui sur le bouton crée un autre contrôleur popover; la bonne chose à faire dans ce cas est de rejeter le popover. Votre réponse permet ce bon comportement. Si vous le câblez directement à partir du bouton dans le storyboard, je ne vois pas comment obtenir le bon comportement.
wcochran
1
Après plusieurs heures frustrantes à essayer de faire en sorte que plusieurs popovers basés sur des séquences fonctionnent correctement, j'ai abandonné et je me suis débarrassé des seques popover en faveur de cette solution. Il utilise en fait moins de code.
mpemburn
Cela n'irait-il pas à l'encontre du but d'avoir des séquences?
Cristik
Le fait de lier ViewController à ViewController a résolu mon problème. Je vous remercie! C'est la meilleure solution
Dr TJ
19

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

Valeur de retour true si la séquence doit être effectuée ou false si elle doit être ignorée.

Exemple :

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
la source
12

Alternativement, c'est un comportement quelque peu mauvais d'offrir un bouton sur lequel un utilisateur ne devrait pas appuyer. Vous pouvez laisser la séquence câblée en position debout, mais commencez avec le bouton désactivé. Câblez ensuite le "editChanged" de l'UITextField à un événement sur le contrôle de vue ala

- (IBAction)nameChanged:(id)sender {
    UITextField *text = (UITextField*)sender;
    [nextButton setEnabled:(text.text.length != 0)];
}
Feu de Kaolin
la source
"Alternativement, c'est un comportement quelque peu mauvais d'offrir un bouton sur lequel un utilisateur ne devrait pas appuyer". Je ne serais pas d'accord avec cela - cela est partiellement vrai mais dépend vraiment du contexte. Il est également mauvais comportement de ne pas guider l'utilisateur - par exemple pour qu'il puisse appuyer sur un bouton et que le système explique ce qui doit être fait en premier. Avec un bouton désactivé ou invisible, les utilisateurs se perdront ou appelleront le support ...
csmith
11

Son facile dans le vif.

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

    return true
}
Zumry Mohamed
la source
3
Hein? Pouvez-vous développer davantage cette réponse? Les réponses uniquement codées ne sont pas très utiles pour les lecteurs ultérieurs ...
Cristik
9

Comme Abraham l'a dit, cochez valide ou non dans la fonction suivante.

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

Et, l' performSegueWithIdentifier:sender:appel par programmation peut être bloqué par écrasement de la méthode suivante. Par défaut, il ne vérifie pas sa validité ou non -shouldPerformSegueWithIdentifier:sender:, nous pouvons le faire manuellement.

- (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
la source
cette partie est-elle [super performSegueWithIdentifier:identifier sender:sender];vraiment vraie?
Ben Wheeler du
@BenWheeler Vous pouvez l'essayer. Si vous remplacez la performSegueWithIdentifier:sender:méthode et n'appelez pas sa superméthode.
AechoLiu
5

Devrait effectuer Segue pour l'ouverture de session

-(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
la source
4

Semblable à la réponse de Kaolin est de laisser la seque câblée au contrôle mais de valider le contrôle en fonction des conditions dans la vue. Si vous tirez sur l'interaction de cellules de tableau, vous devez également définir la propriété userInteractionEnabled ainsi que désactiver les éléments dans la cellule.

Par exemple, j'ai un formulaire dans une vue de table groupée. L'une des cellules mène à une autre tableView qui agit comme sélecteur. Chaque fois qu'un contrôle est modifié dans la vue principale, j'appelle cette méthode

-(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
la source
4

Swift 4 Réponse:

Voici la mise en œuvre de Swift 4 pour annuler la séquence:

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
la source
2

L'autre façon consiste à remplacer la méthode de tableView avec willSelectRowAt et à retourner nil si vous ne souhaitez pas afficher la séquence. showDetails()- est un peu booléen. Dans la plupart des cas, doit être implémenté dans le modèle de données représenté dans la cellule avec indexPath.

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