数据输入的自定义视图的Swift示例(自定义应用程序内键盘)

Sur*_*gch 38 keyboard ios swift custom-keyboard

目标

我想制作一个仅在我的应用程序中使用的自定义键盘,而不是需要安装的系统键盘.

我读过和尝试过的内容

文档

上面的第一篇文章指出:

确保自定义的系统范围键盘确实是您想要开发的.为了仅为您的应用提供完全自定义的键盘,或仅在您的应用中使用自定义键补充系统键盘,iOS SDK提供了其他更好的选择.在iOS文本编程指南的"数据输入自定义视图"中阅读有关自定义输入视图和输入附件视图的信息.

这就是我上面第二篇文章的原因.但是,那篇文章没有足够的细节让我开始.

教程

我能够从上面列表中的第二个教程中获得一个工作键盘.但是,我找不到任何教程,这些教程展示了如何制作一个仅限应用程序的键盘,如数据输入自定义视图文档中所述.

堆栈溢出

在回答当前问题的路上,我也问了(并回答了)这些问题.

有没有人在应用程序自定义键盘中有一个最小的例子(甚至一个按钮)?我不是在寻找一个完整的教程,只是一个概念证明,我可以扩展自己.

Sur*_*gch 70

在此输入图像描述

这是一个基本的应用内键盘.可以使用相同的方法来制作任何键盘布局.以下是需要完成的主要工作:

  • 在.xib文件中创建键盘布局,该文件的所有者是包含UIView子类的.swift文件.
  • 告诉UITextField使用自定义键盘.
  • 使用委托在键盘和主视图控制器之间进行通信.

创建.xib键盘布局文件

  • 在Xcode中,转到文件>新建>文件...> iOS>用户界面>视图以创建.xib文件.
  • 我打电话给我的Keyboard.xib
  • 添加所需的按钮.
  • 使用自动布局约束,这样无论键盘的大小如何,按钮都会相应调整大小.
  • 将文件所有者(不是根视图)设置为Keyboard.swift文件.这是一个常见的错误来源.请参阅最后的注释.

创建.swift UIView子类键盘文件

  • 在Xcode中,转到文件>新建>文件...> iOS>源> Cocoa Touch类以创建.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
        }
    
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 控制从@IBAction.xib文件中的按钮拖动到.swift文件中的方法以将它们全部挂起.

  • 请注意协议和委托代码.有关代表如何工作的简单说明,请参阅此答案.

设置视图控制器

  • 添加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)
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 请注意,视图控制器采用KeyboardDelegate我们在上面定义的协议.

常见错误

如果您收到EXC_BAD_ACCESS错误,可能是因为您将视图的自定义类设置为Keyboard.swift而不是为nib文件的所有者执行此操作.

选择Keyboard.nib,然后选择File's Owner.

在此输入图像描述

确保根视图的自定义类为空.

在此输入图像描述


Ste*_*eve 9

在Suragch的回答的基础上,我需要一个完成和退格按钮,如果你是一个像我这样的菜鸟,你可能会遇到一些错误以及我解决它们的方式.

出现EXC_BAD_ACCESS错误? 包括我:

@objc(classname)
class classname: UIView{ 
}
Run Code Online (Sandbox Code Playgroud)

修复了我的问题但Suragch的更新答案似乎解决了这个更合适/更正确的方法.

得到SIGABRT错误? 另一个愚蠢的事情是以错误的方式拖动连接,导致SIGABRT错误.不要将功能拖动到按钮,而是将按钮拖动到该功能.

添加完成按钮 我将此添加到keyboard.swift中的协议:

protocol KeyboardDelegate: class {
    func keyWasTapped(character: String)
    func keyDone()
}
Run Code Online (Sandbox Code Playgroud)

然后将一个新的IBAction从我的完成按钮连接到keyboard.swift,如下所示:

@IBAction func Done(sender: UIButton) {
    self.delegate?.keyDone()
}
Run Code Online (Sandbox Code Playgroud)

然后跳回我的viewController.swift,在那里我使用这个键盘并在函数keyWasTapped之后添加以下内容:

func keyDone() {
    view.endEditing(true)
}
Run Code Online (Sandbox Code Playgroud)

添加Backspace 这让我感到很沮丧,因为你必须在viewDidLoad()方法中将textField.delegate设置为self(稍后显示).

第一:在keyboard.swift中添加协议func backspace():

protocol KeyboardDelegate: class {
    func keyWasTapped(character: String)
    func keyDone()
    func backspace()
}
Run Code Online (Sandbox Code Playgroud)

第二:连接类似于完成动作的新IBAction:

@IBAction func backspace(sender: UIButton) {
    self.delegate?.backspace()
}
Run Code Online (Sandbox Code Playgroud)

第三:到了出现NumberPad的viewController.swift.

重要说明:在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
}
Run Code Online (Sandbox Code Playgroud)

如果有办法对视图中的所有textField执行此操作,我不确定如何操作.这会很方便......

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()
}
Run Code Online (Sandbox Code Playgroud)

解释这里发生的事情:

  1. 您创建一个包含textField的变量.
  2. 当调用"textFieldDidBeginEditing"时,它设置变量,以便它知道我们正在处理哪个textField.我添加了很多print(),所以我们知道一切都在执行.
  3. 我们的退格函数然后检查我们正在处理的textField并使用.deleteBackward().这将删除光标前的直接字符.

你应该做生意.非常感谢Suragchs帮助我实现这一目标.


Rob*_*Rob 6

关键是使用已经符合的现有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
    }
}
Run Code Online (Sandbox Code Playgroud)

那么你也能:

textField.inputView = NumericKeyboard(target: textField)
Run Code Online (Sandbox Code Playgroud)

产生:

在此处输入图片说明

上面的内容很原始,但是它说明了这个想法:让您拥有输入视图并使用该UIKeyInput协议将键盘输入传达给控件。

另外,请注意使用accessibilityTraits来获得正确的“语音内容”»“语音屏幕”行为。并且如果您将图像用作按钮,请确保也设置accessibilityLabel了。

  • “inputView”的想法是使用您自己的自定义键盘/视图来代替标准键盘。但是,如果您不希望它显示在键盘通常所在的位置,那么也许您根本不想使用“inputView”,而只是放置在某个视图中的一堆按钮,当您点击它们时会更新一些标签。不过,如果您还有其他问题,我建议在此答案下的评论可能不适合进行此讨论。如果您在进行一些研究后没有发现任何内容,只需发布​​您自己的问题...... (3认同)
  • 我想在此键盘中添加“完成”按钮,我添加了该按钮,但现在采取行动如何放弃键盘 (2认同)
  • @RahulPhate - 将“target”从“UIKeyInput?”更改为“(UIResponder &amp; UIKeyInput)?”(并对“init”方法也进行相应的更改)。然后您可以在按钮处理程序中调用“target?.resignFirstResponder()”。 (2认同)
  • @Elin如果你不实例化按钮,你将没有任何东西可以点击。哈哈。如果您泄漏了这些按钮,很可能是因为您泄漏了为其设置了此“inputView”的“UITextField”或“UITextView”。如果您泄漏了正在使用其视图/故事板/笔尖的视图控制器,则可能会发生这种情况。但问题不在于按钮的实例化,无疑是视图和/或视图控制器层次结构中更高的部分。但我刚刚测试了这个,按钮被实例化并释放得很好。简而言之,你的问题出在其他地方。 (2认同)
  • 是的,您希望每个控件都有一个不同的键盘实例(以便每个控件都可以与其各自的“UITextField”进行通信)。所以,是的,这意味着如果每个键盘上有 12 个按钮和 5 个文本字段,那么是的,您正在实例化 60 个按钮。12 (2认同)