web-dev-qa-db-fra.com

Utiliser "Suivant" comme clé de retour

J'utilise la valeur "Suivant" pour la "touche de retour" pour obtenir le bouton Suivant à la place du bouton Terminé, mais (évidemment) son activation ne passe pas automatiquement au prochain champ UITextField selon moi.

Quelle est la bonne façon de faire cela? J'ai vu beaucoup de réponses, mais est-ce que quelqu'un a une solution Swift?

29

Assurez-vous que le délégué est défini dans vos champs de texte et implémentez la méthode textFieldShouldReturn. C'est la méthode appelée lorsque l'utilisateur appuie sur la touche de retour (peu importe à quoi elle ressemble).

La méthode pourrait ressembler à ceci:

func textFieldShouldReturn(textField: UITextField) -> Bool {
    if textField == self.field1 {
        self.field2.becomeFirstResponder()
    }

    return true
}

La logique réelle ici pourrait varier. Il existe de nombreuses approches et je vous déconseillerais fortement une chaîne if/else si vous avez beaucoup de champs de texte, mais l'essentiel est de déterminer quelle vue est actuellement active afin de déterminer quelle vue doit devenir active. Une fois que vous avez déterminé quelle vue doit devenir active, appelez la méthode becomeFirstResponder de cette vue.


Pour une certaine propreté du code, vous pouvez envisager une extension UITextField qui ressemble à ceci:

private var kAssociationKeyNextField: UInt8 = 0

extension UITextField {
    var nextField: UITextField? {
        get {
            return objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField
        }
        set(newField) {
            objc_setAssociatedObject(self, &kAssociationKeyNextField, newField, .OBJC_ASSOCIATION_RETAIN)
        }
    }
}

Et changez ensuite notre méthode textFieldShouldReturn pour ressembler à ceci:

func textFieldShouldReturn(textField: UITextField) -> Bool {
    textField.nextField?.becomeFirstResponder()
    return true
}

Une fois que cela est fait, vous devez simplement définir la nouvelle propriété nextField de chaque champ de texte dans viewDidLoad:

self.field1.nextField = self.field2
self.field2.nextField = self.field3
self.field3.nextField = self.field4
self.field4.nextField = self.field1

Bien que si nous le voulions vraiment, nous pourrions préfixer la propriété avec @IBOutlet, ce qui nous permettrait de connecter notre propriété "nextField" dans le générateur d'interface.

Changez l'extension pour ressembler à ceci:

private var kAssociationKeyNextField: UInt8 = 0

extension UITextField {
    @IBOutlet var nextField: UITextField? {
        get {
            return objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField
        }
        set(newField) {
            objc_setAssociatedObject(self, &kAssociationKeyNextField, newField, .OBJC_ASSOCIATION_RETAIN)
        }
    }
}

Et maintenant, connectez la propriété nextField dans le générateur d'interface:

enter image description here

(Configurez votre délégué pendant que vous êtes ici aussi.)


Et bien sûr, si la propriété nextField renvoie nil, le clavier se cache.

82
nhgrif

Voici un exemple dans Swift:

J'ai créé un écran avec 6 UITextFields. Je leur ai attribué les balises 1 à 6 dans Interface Builder. J'ai également changé la touche de retour à Suivant dans IB. Ensuite, j'ai implémenté les éléments suivants:

import UIKit

// Make your ViewController a UITextFieldDelegate    
class ViewController: UIViewController, UITextFieldDelegate {

    // Use a dictionary to define text field order 1 goes to 2, 2 goes to 3, etc.
    let nextField = [1:2, 2:3, 3:4, 4:5, 5:6, 6:1]

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        // Make ourselves the delegate of the text fields so that textFieldShouldReturn
        // will be called when the user hits the Next/Return key
        for i in 1...6 {
            if let textField = self.view.viewWithTag(i) as? UITextField {
                textField.delegate = self
            }
        }
    }

    // This is called when the user hits the Next/Return key        
    func textFieldShouldReturn(textField: UITextField) -> Bool {
        // Consult our dictionary to find the next field
        if let nextTag = nextField[textField.tag] {
            if let nextResponder = textField.superview?.viewWithTag(nextTag) {
                // Have the next field become the first responder
                nextResponder.becomeFirstResponder()
            }
        }
        // Return false here to avoid Next/Return key doing anything
        return false
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}
8
vacawama

Il n’ya rien de mal avec les autres réponses, c’est juste une approche différente avec l’avantage d’être davantage centré sur OOP - imho (bien que ce soit un peu plus de travail à l’avant, il peut être réutilisé). Dans le storyboard, je commence par ajouter des balises avec une plage distincte (par exemple 800-810) définissant l'ordre spécifique des champs dans lesquels je souhaite passer. Cela présente l'avantage de fonctionner sur toutes les sous-vues de la vue principale afin que l'on puisse naviguer entre les contrôles UITextField et UITextView (et tout autre contrôle) selon les besoins. 

En règle générale, j'essaie généralement d'avoir un message entre contrôleurs de vue et objets de gestionnaire d'événements personnalisé J'utilise donc un message (c'est-à-dire NSNotification) renvoyé au contrôleur de vue à partir d'une classe de délégué personnalisé.

(Gestionnaire de délégué TextField)

Remarque: dans AppDelegate.Swift: laissez defaultCenter = NSNotificationCenter.defaultCenter ()

//Globally scoped
struct MNGTextFieldEvents {
    static let NextButtonTappedForTextField = "MNGTextFieldHandler.NextButtonTappedForTextField"
}

class MNGTextFieldHandler: NSObject, UITextFieldDelegate {

    var fields:[UITextField]? = []

    func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
        return true
    }
    func textFieldDidBeginEditing(textField: UITextField) {
        textField.backgroundColor = UIColor.yellowColor()
    }
    func textFieldDidEndEditing(textField: UITextField) {
        textField.backgroundColor = UIColor.whiteColor()
    }
    func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
        return true
    }
    func textFieldShouldClear(textField: UITextField) -> Bool {
        return false
    }
    func textFieldShouldEndEditing(textField: UITextField) -> Bool {
        return true
    }
    func textFieldShouldReturn(textField: UITextField) -> Bool {

        //passes the message and the textField (with tag) calling the method
        defaultCenter.postNotification(NSNotification(name: MNGTextFieldEvents.NextButtonTappedForTextField, object: textField))

        return false
    }
}

Cela permet à mon contrôleur de vue de rester concentré sur sa tâche principale consistant à gérer la messagerie entre les objets, le modèle et la vue.

(View Controller reçoit un message du délégué et transmet des instructions à l'aide de la fonction advanceToNextField)

Remarque: Dans mon storyboard, les classes de mon gestionnaire personnalisé sont définies à l'aide d'un objet NSObject. Cet objet est lié au storyboard en tant que délégué des contrôles à surveiller. Ce qui provoque l'initialisation automatique de la classe de gestionnaires personnalisés.

class MyViewController: UIViewController {
    @IBOutlet weak var tagsField: UITextField! { didSet {
        (tagsField.delegate as? MNGTextFieldHandler)!.fields?.append(tagsField)
        }
    }
    @IBOutlet weak var titleField: UITextField!{ didSet {
        (titleField.delegate as? MNGTextFieldHandler)!.fields?.append(titleField)
        }
    }
    @IBOutlet weak var textView: UITextView! { didSet {
     (textView.delegate as? MNGTextViewHandler)!.fields?.append(textView)

        }
    }

    private struct Constants {
     static let SelectorAdvanceToNextField = Selector("advanceToNextField:")
}
    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        registerEventObservers()
    }
    override func viewDidDisappear(animated: Bool) {
        super.viewDidDisappear(animated)
        deRegisterEventObservers()
    }

    func advanceToNextField(notification:NSNotification) {
        let currentTag = (notification.object as! UIView).tag

        for aView in self.view.subviews {
            if aView.tag == currentTag + 1 {
                aView.becomeFirstResponder()
            }
        }
    }

    func registerEventObservers () {

        defaultCenter.addObserver(self, selector: Constants.SelectorAdvanceToNextField, name: MNGTextFieldEvents.NextButtonTappedForTextField, object: nil)
    }

    func deRegisterEventObservers() {

        defaultCenter.removeObserver(self, name: MNGTextFieldEvents.NextButtonTappedForTextField, object: nil)
    }

    ....
}
2
Tommie C.

Juste un autre moyen d'obtenir le résultat que j'ai trouvé utile. Mon application avait 11 champs de texte suivis d'une vue texte. Je devais être en mesure de parcourir tous les champs en utilisant la touche suivante, puis de quitter le clavier après la visualisation en mode texte (c’est-à-dire d’autres notes).

Dans le storyboard, je place la balise sur tous les champs (texte et textview) en commençant par 1 à 12, 12 étant le textview.

Je suis sûr qu'il existe d'autres moyens de le faire et cette méthode n'est pas parfaite, mais j'espère que cela aidera quelqu'un.

Dans le code, j'ai écrit ce qui suit:

func textFieldShouldReturn(textField: UITextField) -> Bool {

    let nextTag = textField.tag + 1

    //Handle Textview transition, Textfield programmatically
    if textField.tag == 11 {
        //Current tag is 11, next field is a textview
        self.OtherNotes.becomeFirstResponder()

    } else if nextTag > 11 {
        //12 is the end, close keyboard
        textField.resignFirstResponder()

    } else {
        //Between 1 and 11 cycle through using next button
        let nextResponder = self.view.viewWithTag(nextTag) as? UITextField
        nextResponder?.becomeFirstResponder()

    }

    return false
}

func textFieldDidEndEditing(textField: UITextField) {

    textField.resignFirstResponder()
}

func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {

    //Remove keyboard when clicking Done on keyboard
    if(text == "\n") {
        textView.resignFirstResponder()
        return false
    }
    return true
}
1
sarac