Пример 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)

Это дает:

enter image description here

Вышеприведенное описание довольно примитивно, но оно иллюстрирует идею: создайте собственный вид ввода и используйте протокол UIKeyInput для передачи ввода с клавиатуры на элемент управления.

Также обратите внимание на использование accessibilityTraits для получения правильного поведения "Разговорный контент" "" Говорить на экране ". И если вы используете изображения для своих кнопок, обязательно установите также accessibilityLabel.

Ответ 2

enter image description here

Это базовая клавиатура в приложении. Тот же метод может быть использован для создания практически любой раскладки клавиатуры. Вот основные вещи, которые необходимо сделать:

  • Создайте раскладку клавиатуры в файле .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, а затем выберите "Владелец файла".

enter image description here

Убедитесь, что пользовательский класс для корневого представления пуст.

enter image description here

Ответ 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(). Это удаляет непосредственный символ перед курсором.

И вы должны быть в бизнесе. Большое спасибо Сурагчам за то, что помогли мне добиться этого.