Je veux créer un clavier personnalisé qui n'est utilisé que dans mon application, pas un clavier système qui doit être installé.
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.
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.
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:
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
.
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:
UIView
.UITextField
d'utiliser le clavier personnalisé.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.
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.
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.
Assurez-vous que la classe personnalisée pour la vue racine est vide.
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:
Et vous devriez être en affaires. Un grand merci à Suragchs pour m'avoir aidé à réaliser cela.