Пример Swift пользовательских представлений для ввода данных (настраиваемая клавиатура в приложении)
Цель
Я хочу создать пользовательскую клавиатуру, которая используется только в моем приложении, а не системную клавиатуру, которую необходимо установить.
Что я прочитал и попробовал
Документация
- Руководство по программированию расширений приложений: пользовательская клавиатура
- Пользовательские представления для ввода данных
Первая статья выше гласит:
Убедитесь, что пользовательская общесистемная клавиатура действительно является тем, что вы хотите разработать. Чтобы предоставить полностью настраиваемую клавиатуру только для вашего приложения или дополнить системную клавиатуру настраиваемыми клавишами только в вашем приложении, iOS SDK предоставляет другие, более лучшие варианты. Прочитайте о пользовательских представлениях ввода и вспомогательных видах ввода в Пользовательских представлениях для ввода данных в Руководстве по программированию текста для iOS.
Вот что привело меня ко второй статье выше. Однако в этой статье не было достаточно деталей, чтобы начать меня.
Учебники
- iOS 8: создание пользовательской клавиатуры в Swift
- Как сделать пользовательскую клавиатуру в iOS 8 с помощью Swift
- Xcode 6 Tutorial: iOS 8.0 Простая пользовательская клавиатура в Swift
- Создание пользовательской клавиатуры с помощью расширения приложения iOS 8
Я смог получить рабочую клавиатуру из второго урока в списке выше. Однако я не смог найти никаких учебных пособий, в которых показано, как создать встроенную клавиатуру только в приложении, как описано в документации по пользовательским представлениям для ввода данных.
Переполнение стека
Я также задавал (и отвечал) эти вопросы на пути к ответу на текущий вопрос.
Вопрос
У кого-нибудь есть минимальный пример (даже с одной кнопкой) пользовательской клавиатуры в приложении? Я не ищу целый учебник, просто доказательство концепции, которую я могу расширить на себя.
2 ответа
Это базовая клавиатура в приложении. Тот же метод может быть использован для создания практически любой раскладки клавиатуры. Вот основные вещи, которые необходимо сделать:
- Создайте раскладку клавиатуры в файле.xib, владельцем которого является файл.swift, содержащий
UIView
подкласс. - Скажи
UITextField
использовать пользовательскую клавиатуру. - Используйте делегата для связи между клавиатурой и контроллером основного вида.
Создайте файл раскладки клавиатуры.xib
- В Xcode перейдите в Файл> Создать> Файл... > iOS > Интерфейс пользователя> Вид, чтобы создать файл.xib.
- Я назвал мой Keyboard.xib
- Добавьте кнопки, которые вам нужны.
- Используйте автоматические ограничения макета, чтобы независимо от того, какого размера клавиатура, размер кнопок будет соответственно изменяться.
- Установите в качестве владельца файла (не корневого представления) файл Keyboard.swift. Это распространенный источник ошибок. Смотрите примечание в конце.
Создайте файл клавиатуры подкласса.swift UIView
- В Xcode зайдите в Файл> Создать> Файл... > iOS > Источник> Какао 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 = NSBundle.mainBundle().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, а затем выберите Владелец файла.
Убедитесь, что пользовательский класс для корневого представления пуст.
Ключ в том, чтобы использовать существующие UIKeyInput
протокол, к которому UITextField
уже соответствует. Тогда ваше представление клавиатуры нужно только отправить insertText()
а также deleteBackward()
к контролю.
В следующем примере создается настраиваемая цифровая клавиатура:
class DigitButton: UIButton {
var digit: Int = 0
}
class NumericKeyboard: UIView {
weak var target: UIKeyInput?
var useDecimalSeparator: Bool
var numericButtons: [DigitButton] = (0...9).map {
let button = DigitButton(type: .system)
button.digit = $0
button.setTitle("\($0)", for: .normal)
button.titleLabel?.font = .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 = .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
}()
lazy var decimalButton: UIButton = {
let button = UIButton(type: .system)
let decimalSeparator = Locale.current.decimalSeparator ?? "."
button.setTitle(decimalSeparator, for: .normal)
button.titleLabel?.font = .preferredFont(forTextStyle: .largeTitle)
button.setTitleColor(.black, for: .normal)
button.layer.borderWidth = 0.5
button.layer.borderColor = UIColor.darkGray.cgColor
button.accessibilityTraits = [.keyboardKey]
button.accessibilityLabel = decimalSeparator
button.addTarget(self, action: #selector(didTapDecimalButton(_:)), for: .touchUpInside)
return button
}()
init(target: UIKeyInput, useDecimalSeparator: Bool = false) {
self.target = target
self.useDecimalSeparator = useDecimalSeparator
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 didTapDecimalButton(_ sender: DigitButton) {
target?.insertText(Locale.current.decimalSeparator ?? ".")
}
@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)
if useDecimalSeparator {
subStackView.addArrangedSubview(decimalButton)
} else {
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)
Это дает:
Или, если вам нужен десятичный разделитель, вы можете:
textField.inputView = NumericKeyboard(target: textField, useDecimalSeparator: true)
Вышеупомянутое довольно примитивно, но оно иллюстрирует идею: создайте собственное представление ввода и используйте UIKeyInput
протокол для передачи данных с клавиатуры на элемент управления.
Также обратите внимание на использование accessibilityTraits
для получения правильного поведения "Разговорный контент" "" Говорить на экране ". И если вы используете изображения для своих кнопок, не забудьте установитьaccessibilityLabel
, тоже.
Основываясь на ответе Сурагча, мне понадобилась кнопка "Готово" и "Backspace", и если вы такой нуб, как я, то здесь есть некоторые ошибки, с которыми вы можете столкнуться, и способ, которым я их решил.
Получаете ошибки EXC_BAD_ACCESS? Я включен:
@objc(classname)
class classname: UIView{
}
исправил мою проблему, однако обновленный ответ Сурагча, похоже, решил эту проблему более подходящим / правильным способом.
Получение 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 значение self в методе 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() установите все textFields, которые будут использовать эту клавиатуру. Так что ваш 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
}
Я не уверен, как, если есть способ просто сделать это для всех textFields, которые находятся в представлении. Это было бы удобно...
Далее: все еще в 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()
}
Объяснение того, что здесь происходит:
- Вы делаете переменную, которая будет содержать textField.
- Когда вызывается textFieldDidBeginEditing, он устанавливает переменную, чтобы он знал, с каким textField мы имеем дело. Я добавил много print (), поэтому мы знаем, что все выполняется.
- Наша функция возврата затем проверяет textField, с которым мы имеем дело, и использует.deleteBackward(). Это удаляет непосредственный символ перед курсором.
И ты должен быть в бизнесе. Большое спасибо Suragchs за помощь в этом.