web-dev-qa-db-fra.com

A Swift de vues personnalisées pour la saisie de données (clavier intégré à l'application)

Objectif

Je veux créer un clavier personnalisé qui n'est utilisé que dans mon application, pas un clavier système qui doit être installé.

Ce que j'ai lu et essayé

Documentation

Le premier article ci-dessus déclare:

Assurez-vous qu'un clavier personnalisé à l'échelle du système est bien ce que vous souhaitez développer. Pour fournir un clavier entièrement personnalisé uniquement pour votre application ou pour compléter le clavier système avec des touches personnalisées dans votre application uniquement, le SDK iOS fournit d'autres options améliorées. Pour en savoir plus sur les vues d'entrée personnalisées et les vues d'accessoires d'entrée, consultez Vues personnalisées pour la saisie de données dans le Guide de programmation de texte pour iOS.

C'est ce qui m'a conduit au deuxième article ci-dessus. Cependant, cet article n'avait pas suffisamment de détails pour me permettre de commencer.

Tutoriels

J'ai pu obtenir un clavier fonctionnel à partir du deuxième didacticiel de la liste ci-dessus. Cependant, je n'ai trouvé aucun didacticiel montrant comment créer un clavier intégré à l'application, comme décrit dans la documentation Vues personnalisées pour la saisie de données .

Débordement de pile

J'ai également posé (et répondu) à ces questions sur ma façon de répondre à la question actuelle.

Question

Quelqu'un a-t-il un exemple minimal (avec un seul bouton) d'un clavier personnalisé dans l'application? Je ne recherche pas un tutoriel complet, juste une preuve de concept que je peux développer sur moi-même.

35
Suragch

La clé est d'utiliser le protocole existant UIKeyInput , auquel UITextField est déjà conforme. Ensuite, votre vue clavier n'a qu'à envoyer insertText() et deleteBackward() au contrôle.

L'exemple suivant crée un clavier numérique personnalisé:

class DigitButton: UIButton {
    var digit: Int = 0
}

class NumericKeyboard: UIView {
    weak var target: UIKeyInput?

    var numericButtons: [DigitButton] = (0...9).map {
        let button = DigitButton(type: .system)
        button.digit = $0
        button.setTitle("\($0)", for: .normal)
        button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .largeTitle)
        button.setTitleColor(.black, for: .normal)
        button.layer.borderWidth = 0.5
        button.layer.borderColor = UIColor.darkGray.cgColor
        button.accessibilityTraits = [.keyboardKey]
        button.addTarget(self, action: #selector(didTapDigitButton(_:)), for: .touchUpInside)
        return button
    }

    var deleteButton: UIButton = {
        let button = UIButton(type: .system)
        button.setTitle("⌫", for: .normal)
        button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .largeTitle)
        button.setTitleColor(.black, for: .normal)
        button.layer.borderWidth = 0.5
        button.layer.borderColor = UIColor.darkGray.cgColor
        button.accessibilityTraits = [.keyboardKey]
        button.accessibilityLabel = "Delete"
        button.addTarget(self, action: #selector(didTapDeleteButton(_:)), for: .touchUpInside)
        return button
    }()

    init(target: UIKeyInput) {
        self.target = target
        super.init(frame: .zero)
        configure()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

// MARK: - Actions

extension NumericKeyboard {
    @objc func didTapDigitButton(_ sender: DigitButton) {
        target?.insertText("\(sender.digit)")
    }

    @objc func didTapDeleteButton(_ sender: DigitButton) {
        target?.deleteBackward()
    }
}

// MARK: - Private initial configuration methods

private extension NumericKeyboard {
    func configure() {
        autoresizingMask = [.flexibleWidth, .flexibleHeight]
        addButtons()
    }

    func addButtons() {
        let stackView = createStackView(axis: .vertical)
        stackView.frame = bounds
        stackView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        addSubview(stackView)

        for row in 0 ..< 3 {
            let subStackView = createStackView(axis: .horizontal)
            stackView.addArrangedSubview(subStackView)

            for column in 0 ..< 3 {
                subStackView.addArrangedSubview(numericButtons[row * 3 + column + 1])
            }
        }

        let subStackView = createStackView(axis: .horizontal)
        stackView.addArrangedSubview(subStackView)

        let blank = UIView()
        blank.layer.borderWidth = 0.5
        blank.layer.borderColor = UIColor.darkGray.cgColor

        subStackView.addArrangedSubview(blank)
        subStackView.addArrangedSubview(numericButtons[0])
        subStackView.addArrangedSubview(deleteButton)
    }

    func createStackView(axis: NSLayoutConstraint.Axis) -> UIStackView {
        let stackView = UIStackView()
        stackView.axis = axis
        stackView.alignment = .fill
        stackView.distribution = .fillEqually
        return stackView
    }
}

Ensuite vous pouvez:

textField.inputView = NumericKeyboard(target: textField)

Cela donne:

enter image description here

Ce qui précède est assez primitif, mais il illustre l'idée: créez votre propre vue d'entrée et utilisez le protocole UIKeyInput pour communiquer l'entrée du clavier au contrôle.

Veuillez également noter l'utilisation de accessibilityTraits pour obtenir le comportement correct "Contenu parlé" "" Speak Screen ". Et si vous utilisez des images pour vos boutons, assurez-vous également de définir accessibilityLabel.

4
Rob

enter image description here

Il s'agit d'un clavier de base intégré à l'application. La même méthode peut être utilisée pour créer à peu près n'importe quelle disposition de clavier. Voici les principales choses à faire:

  • Créez la disposition du clavier dans un fichier .xib, dont le propriétaire est un fichier .Swift qui contient une sous-classe UIView.
  • Dites au UITextField d'utiliser le clavier personnalisé.
  • Utilisez un délégué pour communiquer entre le clavier et le contrôleur de vue principal.

Créez le fichier de disposition du clavier .xib

  • Dans Xcode, accédez à Fichier> Nouveau> Fichier ...> iOS> Interface utilisateur> Afficher pour créer le fichier .xib.
  • J'ai appelé le mien Keyboard.xib
  • Ajoutez les boutons dont vous avez besoin.
  • Utilisez les contraintes de disposition automatique de sorte que, quelle que soit la taille du clavier, les boutons se redimensionnent en conséquence.
  • Définissez le propriétaire du fichier (pas la vue racine) comme étant le fichier Keyboard.Swift. Il s'agit d'une source d'erreur courante. Voir la note à la fin.

Créez le fichier de clavier de sous-classe .Swift UIView

  • Dans Xcode, accédez à Fichier> Nouveau> Fichier ...> iOS> Source> Cocoa Touch Class pour créer le fichier .Swift.
  • J'ai appelé le mien Keyboard.Swift
  • Ajoutez le code suivant:

    import UIKit
    
    // The view controller will adopt this protocol (delegate)
    // and thus must contain the keyWasTapped method
    protocol KeyboardDelegate: class {
        func keyWasTapped(character: String)
    }
    
    class Keyboard: UIView {
    
        // This variable will be set as the view controller so that 
        // the keyboard can send messages to the view controller.
        weak var delegate: KeyboardDelegate?
    
        // MARK:- keyboard initialization
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            initializeSubviews()
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            initializeSubviews()
        }
    
        func initializeSubviews() {
            let xibFileName = "Keyboard" // xib extention not included
            let view = Bundle.main.loadNibNamed(xibFileName, owner: self, options: nil)![0] as! UIView
            self.addSubview(view)
            view.frame = self.bounds
        }
    
        // MARK:- Button actions from .xib file
    
        @IBAction func keyTapped(sender: UIButton) {
            // When a button is tapped, send that information to the 
            // delegate (ie, the view controller)
            self.delegate?.keyWasTapped(character: sender.titleLabel!.text!) // could alternatively send a tag value
        }
    
    }
    
  • Faites glisser la commande des boutons du fichier .xib vers le @IBAction méthode dans le fichier .Swift pour les connecter tous.

  • Notez que le protocole et le code délégué. Voir cette réponse pour une explication simple sur le fonctionnement des délégués.

Configurer le contrôleur de vue

  • Ajoutez un UITextField à votre storyboard principal et connectez-le à votre contrôleur de vue avec un IBOutlet. Appelez-le textField.
  • Utilisez le code suivant pour le contrôleur de vue:

    import UIKit
    
    class ViewController: UIViewController, KeyboardDelegate {
    
        @IBOutlet weak var textField: UITextField!
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            // initialize custom keyboard
            let keyboardView = Keyboard(frame: CGRect(x: 0, y: 0, width: 0, height: 300))
            keyboardView.delegate = self // the view controller will be notified by the keyboard whenever a key is tapped
    
            // replace system keyboard with custom keyboard
            textField.inputView = keyboardView
        }
    
        // required method for keyboard delegate protocol
        func keyWasTapped(character: String) {
            textField.insertText(character)
        }
    }
    
  • Notez que le contrôleur de vue adopte le protocole KeyboardDelegate que nous avons défini ci-dessus.

Erreur commune

Si vous obtenez une erreur EXC_BAD_ACCESS, c'est probablement parce que vous définissez la classe personnalisée de la vue en tant que Keyboard.Swift plutôt que de le faire pour le propriétaire du fichier nib.

Sélectionnez Keyboard.nib, puis choisissez Propriétaire du fichier.

enter image description here

Assurez-vous que la classe personnalisée pour la vue racine est vide.

enter image description here

68
Suragch

En s'appuyant sur la réponse de Suragch, j'avais besoin d'un bouton terminé et de retour arrière et si vous êtes un noob comme moi, voici quelques erreurs que vous pourriez rencontrer et la façon dont je les ai résolues.

Vous obtenez des erreurs EXC_BAD_ACCESS? J'ai inclus:

@objc(classname)
class classname: UIView{ 
}

résolu mon problème, mais la réponse mise à jour de Suragch semble résoudre ce problème de la manière la plus appropriée/correcte.

Obtenir une erreur SIGABRT? Une autre chose idiote était de faire glisser les connexions dans le mauvais sens, provoquant une erreur SIGABRT. Ne faites pas glisser la fonction vers le bouton, mais plutôt le bouton vers la fonction.

Ajout d'un bouton Terminé J'ai ajouté cela au protocole dans le clavier.

protocol KeyboardDelegate: class {
    func keyWasTapped(character: String)
    func keyDone()
}

Ensuite, j'ai connecté une nouvelle IBAction de mon bouton terminé au clavier.

@IBAction func Done(sender: UIButton) {
    self.delegate?.keyDone()
}

puis je suis revenu à mon viewController.Swift où j'utilise ce clavier et j'ai ajouté ceci après la touche de fonction WasTapped:

func keyDone() {
    view.endEditing(true)
}

Adding Backspace Cela m'a beaucoup déclenché, car vous devez définir le textField.delegate sur self dans la méthode viewDidLoad () (présentée plus loin).

Premièrement: Dans le clavier, ajouter rapidement au backc de la fonction func ():

protocol KeyboardDelegate: class {
    func keyWasTapped(character: String)
    func keyDone()
    func backspace()
}

Deuxièmement: Connectez une nouvelle IBAction similaire à l'action Terminé:

@IBAction func backspace(sender: UIButton) {
    self.delegate?.backspace()
}

Troisièmement: Vers le viewController.Swift où le pavé numérique apparaît.

Important: Dans viewDidLoad (), définissez tous les textFields qui utiliseront ce clavier. Votre viewDidLoad () devrait donc ressembler à ceci:

 override func viewDidLoad() {
    super.viewDidLoad()

    self.myTextField1.delegate = self
    self.myTextField2.delegate = self

    // initialize custom keyboard
    let keyboardView = keyboard(frame: CGRect(x: 0, y: 0, width: 0, height: 240))
    keyboardView.delegate = self // the view controller will be notified by the keyboard whenever a key is tapped

    // replace system keyboard with custom keyboard
    myTextField1.inputView = keyboardView
    myTextField2.inputView = keyboardView
}

Je ne sais pas comment, s'il existe un moyen de le faire pour tous les textFields qui sont dans la vue. Ce serait pratique ...

Forth: Toujours dans viewController.Swift, nous devons ajouter une variable et deux fonctions. Il ressemblera à ceci:

var activeTextField = UITextField()

func textFieldDidBeginEditing(textField: UITextField) {
    print("Setting Active Textfield")
    self.activeTextField = textField
    print("Active textField Set!")
}

func backspace() {
    print("backspaced!")
    activeTextField.deleteBackward()
}

Explication de ce qui se passe ici:

  1. Vous créez une variable qui contiendra un textField.
  2. Lorsque le "textFieldDidBeginEditing" est appelé, il définit la variable afin qu'il sache avec quel textField nous avons affaire. J'ai ajouté beaucoup de copies () afin que nous sachions que tout est en cours d'exécution.
  3. Notre fonction de retour arrière vérifie ensuite le champ de texte avec lequel nous traitons et utilise .deleteBackward (). Cela supprime le caractère immédiat devant le curseur.

Et vous devriez être en affaires. Un grand merci à Suragchs pour m'avoir aidé à réaliser cela.

9
Steve