ios 数据输入自定义视图的 Swift 示例(自定义应用内键盘)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/33474771/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
A Swift example of Custom Views for Data Input (custom in-app keyboard)
提问by Suragch
Goal
目标
I want to make a custom keyboard that is only used within my app, not a system keyboard that needs to be installed.
我想制作一个仅在我的应用程序中使用的自定义键盘,而不是需要安装的系统键盘。
What I have read and tried
我读过和尝试过的
Documentation
文档
The first article above states:
上面的第一篇文章指出:
Make sure a custom, systemwide keyboard is indeed what you want to develop. To provide a fully custom keyboard for just your app or to supplement the system keyboard with custom keys in just your app, the iOS SDK provides other, better options. Read about custom input views and input accessory views in Custom Views for Data Input in Text Programming Guide for iOS.
确保自定义的全系统键盘确实是您想要开发的。为了仅为您的应用程序提供完全自定义的键盘或仅在您的应用程序中使用自定义键补充系统键盘,iOS SDK 提供了其他更好的选择。在 iOS 文本编程指南中的数据输入自定义视图中阅读自定义输入视图和输入附件视图。
That is what led me to the second article above. However, that article did not have enough detail to get me started.
这就是让我看到上面第二篇文章的原因。然而,那篇文章没有足够的细节让我开始。
Tutorials
教程
- iOS 8: Creating a Custom Keyboard in Swift
- How to make a custom keyboard in iOS 8 using Swift
- Xcode 6 Tutorial: iOS 8.0 Simple Custom Keyboard in Swift
- Creating a Custom Keyboard Using iOS 8 App Extension
- iOS 8:在 Swift 中创建自定义键盘
- 如何使用 Swift 在 iOS 8 中制作自定义键盘
- Xcode 6 教程:iOS 8.0 Swift 中的简单自定义键盘
- 使用 iOS 8 App Extension 创建自定义键盘
I was able to get a working keyboard from the second tutorial in the list above. However, I couldn't find any tutorials that showed how to make an in app only keyboard as described in the Custom Views for Data Inputdocumentation.
我能够从上面列表中的第二个教程中获得一个可以工作的键盘。但是,我找不到任何教程来展示如何按照数据输入的自定义视图文档中的描述制作仅限应用程序的键盘。
Stack Overflow
堆栈溢出
I also asked (and answered) these questions on my way to answering the current question.
在回答当前问题的过程中,我也提出了(并回答了)这些问题。
Question
题
Does anyone have a minimal example (with even one button) of an in app custom keyboard? I am not looking for a whole tutorial, just a proof of concept that I can expand on myself.
有没有人有一个应用程序内自定义键盘的最小示例(甚至一个按钮)?我不是在寻找完整的教程,只是一个我可以扩展自己的概念证明。
采纳答案by Rob
The key is to use the existing UIKeyInput
protocol, to which UITextField
already conforms. Then your keyboard view need only to send insertText()
and deleteBackward()
to the control.
关键是使用已经符合的现有UIKeyInput
协议UITextField
。然后你的键盘视图只需要发送insertText()
和deleteBackward()
控制。
The following example creates a custom numeric keyboard:
以下示例创建一个自定义数字键盘:
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 = textField.inputView = NumericKeyboard(target: textField)
button.setTitle("\(textField.inputView = NumericKeyboard(target: textField, useDecimalSeparator: true)
)", 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
}
}
Then you can:
然后你可以:
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
}
}
That yields:
这产生:
Or, if you want a decimal separator, too, you can:
或者,如果您也需要小数点分隔符,您可以:
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
}
}
The above is fairly primitive, but it illustrates the idea: Make you own input view and use the UIKeyInput
protocol to communicate keyboard input to the control.
上面的内容相当原始,但它说明了这个想法:让您拥有自己的输入视图并使用UIKeyInput
协议将键盘输入传达给控件。
Also please note the use of accessibilityTraits
to get the correct “Spoken Content” ? “Speak Screen” behavior. And if you use images for your buttons, make sure to set accessibilityLabel
, too.
还请注意使用accessibilityTraits
来获得正确的“口语内容”?“朗读屏幕”行为。如果您使用图像作为按钮,请确保也设置accessibilityLabel
。
回答by Suragch
This is a basic in-app keyboard. The same method could be used to make just about any keyboard layout. Here are the main things that need to be done:
这是一个基本的应用内键盘。相同的方法可用于制作几乎任何键盘布局。以下是需要做的主要事情:
- Create the keyboard layout in an .xib file, whose owner is a .swift file that contains a
UIView
subclass. - Tell the
UITextField
to use the custom keyboard. - Use a delegate to communicate between the keyboard and the main view controller.
- 在 .xib 文件中创建键盘布局,其所有者是包含
UIView
子类的 .swift 文件。 - 告诉
UITextField
使用自定义键盘。 - 使用委托在键盘和主视图控制器之间进行通信。
Create the .xib keyboard layout file
创建 .xib 键盘布局文件
- In Xcode go to File > New > File... > iOS > User Interface > Viewto create the .xib file.
- I called mine Keyboard.xib
- Add the buttons that you need.
- Use auto layout constraints so that no matter what size the keyboard is, the buttons will resize accordingly.
- Set the File's Owner (not the root view) to be the Keyboard.swift file. This is a common source of error. See the note at the end.
- 在 Xcode 中,转到File > New > File... > iOS > User Interface > View创建 .xib 文件。
- 我叫我的 Keyboard.xib
- 添加您需要的按钮。
- 使用自动布局约束,这样无论键盘大小如何,按钮都会相应地调整大小。
- 将 File's Owner(不是根视图)设置为 Keyboard.swift 文件。这是一个常见的错误来源。见文末注释。
Create the .swift UIView subclass keyboard file
创建 .swift UIView 子类键盘文件
- In Xcode go to File > New > File... > iOS > Source > Cocoa Touch Classto create the .swift file.
- I called mine Keyboard.swift
Add the following code:
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) } }
Control drag from the buttons in the .xib file to the
@IBAction
method in the .swift file to hook them all up.- Note that the protocol and delegate code. See this answerfor a simple explanation about how delegates work.
- 在 Xcode 中,转到File > New > File... > iOS > Source > Cocoa Touch Class来创建 .swift 文件。
- 我打电话给我的 Keyboard.swift
添加以下代码:
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) } }
控制从
@IBAction
.xib 文件中的按钮拖动到.swift 文件中的方法以将它们全部连接起来。- 注意协议和委托代码。有关委托如何工作的简单解释,请参阅此答案。
Set up the View Controller
设置视图控制器
- Add a
UITextField
to your main storyboard and connect it to your view controller with anIBOutlet
. Call ittextField
. Use the following code for the View Controller:
@objc(classname) class classname: UIView{ }
Note that the view controller adopts the
KeyboardDelegate
protocol that we defined above.
- 将 a 添加
UITextField
到您的主故事板并使用IBOutlet
. 叫它textField
。 对视图控制器使用以下代码:
protocol KeyboardDelegate: class { func keyWasTapped(character: String) func keyDone() }
请注意,视图控制器采用
KeyboardDelegate
我们上面定义的协议。
Common error
常见错误
If you are getting an EXC_BAD_ACCESS error, it is probably because you set the view's custom class as Keyboard.swift rather than do this for the nib File's Owner.
如果您收到 EXC_BAD_ACCESS 错误,可能是因为您将视图的自定义类设置为 Keyboard.swift 而不是为 nib 文件的所有者执行此操作。
Select Keyboard.nib and then choose File's Owner.
选择 Keyboard.nib,然后选择 File's Owner。
Make sure that the custom class for the root view is blank.
确保根视图的自定义类为空。
回答by Steve
Building on Suragch's answer, I needed a done and backspace button and if you're a noob like me heres some errors you might encounter and the way I solved them.
基于 Suragch 的回答,我需要一个完成和退格按钮,如果你是像我这样的菜鸟,这里有一些你可能会遇到的错误以及我解决它们的方式。
Getting EXC_BAD_ACCESS errors?I included:
收到 EXC_BAD_ACCESS 错误?包括我:
@IBAction func Done(sender: UIButton) {
self.delegate?.keyDone()
}
fixed my issue however Suragch's updated answer seems to solve this the more appropriate/correct way.
修复了我的问题,但是 Suragch 的更新答案似乎以更合适/更正确的方式解决了这个问题。
Getting SIGABRT Error?Another silly thing was dragging the connections the wrong way, causing SIGABRT error. Do not drag from the function to the button but instead the button to the function.
收到 SIGABRT 错误?另一个愚蠢的事情是以错误的方式拖动连接,导致 SIGABRT 错误。不要从函数拖到按钮,而是将按钮拖到函数。
Adding a Done ButtonI added this to the protocol in keyboard.swift:
添加一个完成按钮我将它添加到 keyboard.swift 中的协议中:
func keyDone() {
view.endEditing(true)
}
Then connected a new IBAction from my done button to keyboard.swift like so:
然后将一个新的 IBAction 从我的完成按钮连接到 keyboard.swift ,如下所示:
protocol KeyboardDelegate: class {
func keyWasTapped(character: String)
func keyDone()
func backspace()
}
and then jumped back to my viewController.swift where i am using this keyboard and added this following after the function keyWasTapped:
然后跳回到我正在使用这个键盘的 viewController.swift 并在函数 keyWasTapped 之后添加以下内容:
@IBAction func backspace(sender: UIButton) {
self.delegate?.backspace()
}
Adding BackspaceThis tripped me up a lot, because you must set the textField.delegate to self in the viewDidLoad() method (shown later).
添加 Backspace这让我很受打击,因为您必须在 viewDidLoad() 方法中将 textField.delegate 设置为 self(稍后显示)。
First:In keyboard.swift add to the protocol func backspace():
首先:在 keyboard.swift 中添加到协议 func backspace():
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
}
Second:Connect a new IBAction similar to the Done action:
第二:连接一个类似于 Done 动作的新 IBAction:
var activeTextField = UITextField()
func textFieldDidBeginEditing(textField: UITextField) {
print("Setting Active Textfield")
self.activeTextField = textField
print("Active textField Set!")
}
func backspace() {
print("backspaced!")
activeTextField.deleteBackward()
}
Third:Over to the viewController.swift where the NumberPad is appearing.
第三:转到出现 NumberPad 的 viewController.swift 。
Important:In viewDidLoad() set all textFields that will be using this keyboard. So your viewDidLoad() should look something like this:
重要提示:在 viewDidLoad() 中设置将使用此键盘的所有文本字段。所以你的 viewDidLoad() 应该是这样的:
##代码##I'm not sure how to, if there is a way to just do this to all textFields that are in the view. This would be handy...
如果有办法对视图中的所有 textFields 执行此操作,我不确定如何操作。这会很方便...
Forth:Still in viewController.swift we need to add a variable and two functions. It will look like this:
第四:仍然在 viewController.swift 中我们需要添加一个变量和两个函数。它看起来像这样:
##代码##Explanation of whats happening here:
对这里发生的事情的解释:
- You make a variable that will hold a textField.
- When the "textFieldDidBeginEditing" is called it sets the variable so it knows which textField we are dealing with. I've added a lot of prints() so we know everything is being executed.
- Our backspace function then checks the textField we are dealing with and uses .deleteBackward(). This removes the immediate character before the cursor.
- 您创建了一个将保存 textField 的变量。
- 当“textFieldDidBeginEditing”被调用时,它会设置变量,因此它知道我们正在处理哪个 textField。我添加了很多 prints() 所以我们知道一切都在执行中。
- 然后我们的退格函数检查我们正在处理的 textField 并使用 .deleteBackward()。这将删除光标前的直接字符。
And you should be in business. Many thanks to Suragchs for helping me get this happening.
你应该做生意。非常感谢 Suragchs 帮助我实现这一目标。