Пример Swift для пользовательских представлений для ввода данных (обычная клавиатура в приложении)
Цель
Я хочу создать пользовательскую клавиатуру, которая используется только в моем приложении, а не системную клавиатуру, которая должна быть установлена.
То, что я прочитал и попробовал
Documentation
В первой статье выше говорится:
Убедитесь, что пользовательская, системная клавиатура действительно то, что вы хотите развиваться. Предоставить полностью пользовательскую клавиатуру для вашего приложения или дополняйте системную клавиатуру пользовательскими ключами только в вашем приложении, IOS SDK предоставляет другие, лучшие варианты. Читайте о представлениях пользовательского ввода и представления дополнительных аксессуаров в пользовательских представлениях для ввода данных в тексте Руководство по программированию для iOS.
Вот что привело меня ко второй статье выше. Однако в этой статье не было достаточно деталей, чтобы начать меня.
Учебники
Мне удалось получить рабочую клавиатуру из второго учебника в списке выше. Тем не менее, я не мог найти никаких учебных пособий, в которых показано, как сделать приложение только на клавиатуре, как описано в Пользовательские представления для ввода данных.
Переполнение стека
Я также задал (и ответил) эти вопросы на своем пути к ответу на текущий вопрос.
Вопрос
Есть ли у кого-нибудь минимальный пример (с одной кнопкой) в пользовательской клавиатуре приложения? Я не ищу целую учебную программу, просто доказательство концепции, которую я могу расширить сам.
Ответы
Ответ 1
Ключ должен использовать существующий протокол UIKeyInput
, которому UITextField
уже соответствует. Тогда вашему представлению клавиатуры нужно только отправить insertText()
и deleteBackward()
на элемент управления.
В следующем примере создается настраиваемая цифровая клавиатура:
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
}
}
Тогда вы можете:
textField.inputView = NumericKeyboard(target: textField)
Это дает:
Вышеприведенное описание довольно примитивно, но оно иллюстрирует идею: создайте собственный вид ввода и используйте протокол UIKeyInput
для передачи ввода с клавиатуры на элемент управления.
Также обратите внимание на использование accessibilityTraits
для получения правильного поведения "Разговорный контент" "" Говорить на экране ". И если вы используете изображения для своих кнопок, обязательно установите также accessibilityLabel
.
Ответ 2
Это базовая клавиатура в приложении. Тот же метод может быть использован для создания практически любой раскладки клавиатуры. Вот основные вещи, которые необходимо сделать:
- Создайте раскладку клавиатуры в файле .xib, владельцем которого является файл .swift, содержащий подкласс
UIView
.
- Скажите
UITextField
, чтобы использовать пользовательскую клавиатуру.
- Используйте делегата для связи между клавиатурой и контроллером основного вида.
Создайте файл раскладки .xib
- В Xcode перейдите в Файл> Создать> Файл...> iOS> Пользовательский интерфейс> Вид, чтобы создать файл .xib.
- Я позвонил в мой Keyboard.xib
- Добавьте нужные вам кнопки.
- Используйте автоматические ограничения макета, чтобы независимо от размера клавиатуры размер кнопок соответственно изменялся.
- Установите в качестве владельца файла (не корневого представления) файл Keyboard.swift. Это распространенный источник ошибок. Смотрите примечание в конце.
Создайте файл клавиатуры подкласса .swift UIView
- В Xcode перейдите в Файл> Создать> Файл...> iOS> Источник> Cocoa Touch Class, чтобы создать файл .swift.
- Я позвонил в мой Keyboard.swift
Добавьте следующий код:
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
}
}
Управляйте перетаскиванием от кнопок в файле .xib к методу @IBAction
в файле .swift, чтобы соединить их все.
- Обратите внимание, что протокол и код делегата. См. этот ответ для простого объяснения того, как работают делегаты.
Настройте View Controller
- Добавьте
UITextField
в основную раскадровку и подключите его к контроллеру вида с помощью IBOutlet
. Назовите это textField
.
Используйте следующий код для View Controller:
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)
}
}
Обратите внимание, что контроллер представления принимает протокол KeyboardDelegate
, который мы определили выше.
Распространенная ошибка
Если вы получаете ошибку EXC_BAD_ACCESS, возможно, это связано с тем, что пользовательский класс представления установлен как Keyboard.swift, а не для владельца файла пера.
Выберите Keyboard.nib, а затем выберите "Владелец файла".
Убедитесь, что пользовательский класс для корневого представления пуст.
Ответ 3
Основываясь на ответе Сурагха, мне нужна была сделанная кнопка и кнопка backspace, и если вы ноб вроде меня, есть некоторые ошибки, с которыми вы могли столкнуться, и то, как я их решал.
Получение ошибок EXC_BAD_ACCESS?
Я включил:
@objc(classname)
class classname: UIView{
}
исправил мою проблему, однако, по мнению Suragch, ответ был решен более правильным/правильным способом.
Получение ошибки SIGABRT?
Еще одна глупость - перетаскивание соединений неправильным образом, что вызвало ошибку SIGABRT. Не перетаскивайтесь от функции к кнопке, а вместо нее - к функции.
Добавление кнопки "Готово"
Я добавил это в протокол в keyboard.swift:
protocol KeyboardDelegate: class {
func keyWasTapped(character: String)
func keyDone()
}
Затем подключен новый IBAction от моей сделанной кнопки к клавиатуре. Переходите так:
@IBAction func Done(sender: UIButton) {
self.delegate?.keyDone()
}
а затем вернулась к моему viewController.swift, где я использую эту клавиатуру, и добавил следующее после функции keyWasTapped:
func keyDone() {
view.endEditing(true)
}
Добавление Backspace
Это очень сильно повлияло на меня, потому что вы должны установить textField.delegate для себя в методе viewDidLoad() (показано ниже).
Сначала: В keyboard.swift добавьте в протокол func backspace():
protocol KeyboardDelegate: class {
func keyWasTapped(character: String)
func keyDone()
func backspace()
}
Во-вторых: Подключите новый IBAction, аналогичный действию Done:
@IBAction func backspace(sender: UIButton) {
self.delegate?.backspace()
}
В-третьих: Перейдите к viewController.swift, где появляется NumberPad.
Важно: В viewDidLoad() установлены все текстовые поля, которые будут использовать эту клавиатуру. Поэтому ваш viewDidLoad() должен выглядеть примерно так:
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
}
Я не уверен, как это сделать, если есть способ сделать это только для всех текстовых полей, которые находятся в представлении. Это было бы удобно...
Forth: Еще в viewController.swift нам нужно добавить переменную и две функции. Он будет выглядеть следующим образом:
var activeTextField = UITextField()
func textFieldDidBeginEditing(textField: UITextField) {
print("Setting Active Textfield")
self.activeTextField = textField
print("Active textField Set!")
}
func backspace() {
print("backspaced!")
activeTextField.deleteBackward()
}
Объяснение происходящего здесь:
- Вы создаете переменную, которая будет содержать текстовый элемент.
- Когда вызывается "textFieldDidBeginEditing", он устанавливает переменную, чтобы он знал, с каким текстовым полем мы имеем дело. Я добавил много отпечатков(), поэтому мы знаем, что все выполняется.
- Функция backspace затем проверяет textField, с которым мы имеем дело, и использует .deleteBackward(). Это удаляет непосредственный символ перед курсором.
И вы должны быть в бизнесе.
Большое спасибо Сурагчам за то, что помогли мне добиться этого.