前言:
某些业务领域要求使用自定义键盘而非系统内置的键盘,这时候可选择基于View类来完成一个键盘的开发。这里模拟一个简单的数字键盘,来演示一个自定义键盘的完成。
业务模拟:
某ViewController内存在一个这样的UITextField,该UITextField点击后可弹出我们自定义的键盘(称之为MyKeyBoard),而系统内置的键盘则不会被弹出。另外,MyKeyBoard内存在0~9内这10个数值,以及一个DEL键(删除键)。每个按键的作用与常见的按键功能相同,不再赘述。当用户输入完后一个特定的动作(对应委托方法:textFieldShouldBeginEditing()
) 则会将键盘收回。
编码:
View部分:
采用SnapKit进行布局。下面是一个非常简单易懂的思路:
- 按键总计共12个,即每行3个每列4个。可按从上至下、从左到右分为1、2、3、4、5、6、7、8、9、*、0、DEL。
- 最左侧按键其对于左侧的约束相同,即按键1、4、7、* 这4个按键的相对于根视图左侧约束相同。
- 位于中间的按键其X轴都位于其根视图的中央,即3、6、9、0这4个按键的
centerX()
等于根视图。 - 位于最右侧的按键相对于最右侧约束都相同,即按键2、5、8、DEL这4个按键相对于根视图右侧约束都相同。
- 每一行的按键都位于其上一行的按键的下方,即下一行的按键的
top.equalTo(上一行按键.snp.bottom).offset(偏移值)
;第一行按键直接进行布局,无须参考上一行按键。 - 可设置一UIButton泛型集合,用以保存创建的按键变量进行引用。
var WIDTH: Int = Int(UIScreen.main.bounds.size.width)
var HEIGHT: Int = Int(UIScreen.main.bounds.size.height)
var keyBoardTitles: Array<String> = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "0", "Del"]
private func installKeyButton(){
var fowardButtonArray: Array<UIButton> = Array<UIButton>()
// Create custom button type.
for i in 0..<12 {
let button = UIButton(type: .system)
button.tag = i
button.addTarget(self, action: #selector(buttonEvent(button:)), for: UIControl.Event.touchDown)
button.isEnabled = true
button.isUserInteractionEnabled = true
button.titleLabel?.font = .boldSystemFont(ofSize: 21)
button.setTitleColor(UIColor.black, for: UIControl.State.normal)
button.setTitle(keyBoardTitles[i], for: UIControl.State.normal)
// Put the index of every cow's last position.
// It's meanning of next cow's top equals foward cow's UIButton's bottom.
if (i == 2) || (i == 5) || (i == 8) || (i == 11){
fowardButtonArray.append(button)
}
self.addSubview(button)
// Judgement if the button is licated in keyboard matrix.
if (i == 0) || (i == 3) || (i == 6) || (i == 9){
// If keyboard of button is not located in first cow.
if (i == 3) || (i == 6) || (i == 9) {
button.snp.makeConstraints{ make in
make.width.equalTo((WIDTH / 3) - 60)
make.height.equalTo(40)
make.left.equalTo(self.snp.left).offset(5)
make.top.equalTo(fowardButtonArray[returnIndex(index: i)].snp.bottom).offset(30)
}
} else {
button.snp.makeConstraints{ make in
make.width.equalTo((WIDTH / 3) - 60)
make.height.equalTo(40)
make.left.equalTo(self.snp.left).offset(5)
}
}
} else if (i == 1) || (i == 4) || (i == 7) || (i == 10){
// If keyboard of button is not located in first cow.
if (i == 4) || (i == 7) || (i == 10) {
button.snp.makeConstraints{ make in
make.width.equalTo((WIDTH / 3) - 60)
make.height.equalTo(40)
make.centerX.equalToSuperview()
make.top.equalTo(fowardButtonArray[returnIndex(index: i)].snp.bottom).offset(30)
}
} else {
button.snp.makeConstraints{ make in
make.width.equalTo((WIDTH / 3) - 60)
make.height.equalTo(40)
make.centerX.equalToSuperview()
}
}
} else if (i == 2) || (i == 5) || (i == 8) || (i == 11){
// If keyboard of button is not located in first cow.
if (i == 5) || (i == 8) || (i == 11) {
button.snp.makeConstraints{ make in
make.width.equalTo((WIDTH / 3) - 60)
make.height.equalTo(40)
make.right.equalTo(self.snp.right).offset(-5)
make.top.equalTo(fowardButtonArray[returnIndex(index: i)].snp.bottom).offset(30)
}
} else {
button.snp.makeConstraints{ make in
make.width.equalTo((WIDTH / 3) - 60)
make.height.equalTo(40)
make.right.equalTo(self.snp.right).offset(-5)
}
}
}
}
}
// MARK: - Business block.
// Return a concrete cow index by colum index.
private func returnIndex(index: Int) -> Int{
if (index == 3) || (index == 4) || (index == 5) {
return 0
} else if (index == 6) || (index == 7) || (index == 8) {
return 1
} else if (index == 9) || (index == 10) || (index == 11) {
return 2
} else {
return 0
}
}
响应部分:
响应部分具体有以下几个部分:
- 每个按键的响应功能。
- 弹出。
- 消失。
弹出与消失很简单,依附于UIView即可完成这类操作。下面是每个按键的响应部分分析:
主VC弹出MyKeyBoardView后,后者的按键事件要能传递到前者内的UITextField,且要保证健壮性(DEL键不能操纵其它变量为nil类型)使用协议与代理可很容易完成这点。下面是MyKeyBoardView的全部代码:
//
// MyKeyBoardView.swift
// Compent
//
// Created by Eldest's MacBook on 2021/8/23.
//
import UIKit
import SnapKit
class MyKeyBoardView: UIView {
public var delegate: KeyBoardViewProtocol?
// MARK: - Global variables.
var WIDTH: Int = Int(UIScreen.main.bounds.size.width)
var HEIGHT: Int = Int(UIScreen.main.bounds.size.height)
var keyBoardTitles: Array<String> = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "0", "Del"]
var tempString: String = ""
override init(frame: CGRect) {
super.init(frame: frame)
initConfig()
installKeyButton()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Init configuration.
private func initConfig(){
self.backgroundColor = UIColor(red: 0.99, green: 0.99, blue: 0.998, alpha: 1.0)
}
// MARK: - Install widget.
private func installKeyButton(){
var fowardButtonArray: Array<UIButton> = Array<UIButton>()
// Create custom button type.
for i in 0..<12 {
let button = UIButton(type: .system)
button.tag = i
button.addTarget(self, action: #selector(buttonEvent(button:)), for: UIControl.Event.touchDown)
button.isEnabled = true
button.isUserInteractionEnabled = true
button.titleLabel?.font = .boldSystemFont(ofSize: 21)
button.setTitleColor(UIColor.black, for: UIControl.State.normal)
button.setTitle(keyBoardTitles[i], for: UIControl.State.normal)
// Put the index of every cow's last position.
// It's meanning of next cow's top equals foward cow's UIButton's bottom.
if (i == 2) || (i == 5) || (i == 8) || (i == 11){
fowardButtonArray.append(button)
}
self.addSubview(button)
// Judgement if the button is licated in keyboard matrix.
if (i == 0) || (i == 3) || (i == 6) || (i == 9){
// If keyboard of button is not located in first cow.
if (i == 3) || (i == 6) || (i == 9) {
button.snp.makeConstraints{ make in
make.width.equalTo((WIDTH / 3) - 60)
make.height.equalTo(40)
make.left.equalTo(self.snp.left).offset(5)
make.top.equalTo(fowardButtonArray[returnIndex(index: i)].snp.bottom).offset(30)
}
} else {
button.snp.makeConstraints{ make in
make.width.equalTo((WIDTH / 3) - 60)
make.height.equalTo(40)
make.left.equalTo(self.snp.left).offset(5)
}
}
} else if (i == 1) || (i == 4) || (i == 7) || (i == 10){
// If keyboard of button is not located in first cow.
if (i == 4) || (i == 7) || (i == 10) {
button.snp.makeConstraints{ make in
make.width.equalTo((WIDTH / 3) - 60)
make.height.equalTo(40)
make.centerX.equalToSuperview()
make.top.equalTo(fowardButtonArray[returnIndex(index: i)].snp.bottom).offset(30)
}
} else {
button.snp.makeConstraints{ make in
make.width.equalTo((WIDTH / 3) - 60)
make.height.equalTo(40)
make.centerX.equalToSuperview()
}
}
} else if (i == 2) || (i == 5) || (i == 8) || (i == 11){
// If keyboard of button is not located in first cow.
if (i == 5) || (i == 8) || (i == 11) {
button.snp.makeConstraints{ make in
make.width.equalTo((WIDTH / 3) - 60)
make.height.equalTo(40)
make.right.equalTo(self.snp.right).offset(-5)
make.top.equalTo(fowardButtonArray[returnIndex(index: i)].snp.bottom).offset(30)
}
} else {
button.snp.makeConstraints{ make in
make.width.equalTo((WIDTH / 3) - 60)
make.height.equalTo(40)
make.right.equalTo(self.snp.right).offset(-5)
}
}
}
}
}
// MARK: - Business block.
// Return a concrete cow index by colum index.
private func returnIndex(index: Int) -> Int{
if (index == 3) || (index == 4) || (index == 5) {
return 0
} else if (index == 6) || (index == 7) || (index == 8) {
return 1
} else if (index == 9) || (index == 10) || (index == 11) {
return 2
} else {
return 0
}
}
// According to buttons's tag (parameter is index) to return KeyBoard's real content in the
// world.
private func returnContent(index: Int) -> String{
switch index {
case 0:
return "1"
case 1:
return "2"
case 2:
return "3"
case 3:
return "4"
case 4:
return "5"
case 5:
return "6"
case 6:
return "7"
case 7:
return "8"
case 8:
return "9"
case 9:
return "*"
case 10:
return "0"
case 11:
return "del"
default:
return ""
}
}
// MARK: - Event block.
@objc func show(){
UIApplication.shared.keyWindow?.addSubview(self)
UIView.animate(withDuration: 0.55, animations: {
self.alpha = 1.0
self.center = CGPoint(x: (Int(self.frame.width) / 2), y: self.HEIGHT - 138)
})
}
@objc func dismissView(){
UIView.animate(withDuration: 0.3, animations: {
self.alpha = 0
}) { (true) in
self.removeFromSuperview()
}
}
@objc func buttonEvent(button: UIButton){
// If touch down the DEL button.
if button.tag == 11 {
// If currently tempString's count equals 0 and which means tempString should not
// call the menthod of removeLast().
if tempString.count != 0 {
tempString.removeLast()
delegate?.sentData(tempString)
}
} else {
tempString.append(returnContent(index: button.tag))
delegate?.sentData(tempString)
}
}
}
// MARK: - Protocol block.
protocol KeyBoardViewProtocol {
func sentData(_ data: String)
}
主VC:
主VC内需要注意就是响应者的问题。虚拟机内无内置键盘,因此一定会弹出我们的自定义键盘,而真机则会将二者均弹出。
这就需要当textFieldShouldBeginEditing()
被调用时我们将MyKeyBoardView注册为第一响应者,当textFieldShouldReturn()
被调用时失去焦点,卸载第一响应者的身份。
import UIKit
class KeyBoardViewController: BaseViewController, UITextFieldDelegate, KeyBoardViewProtocol {
func sentData(_ data: String) {
self.textField?.text = data
}
private var textField: UITextField!
private var tempString: String = ""
private var mKeyBoardView: MyKeyBoardView!
private func initView(){
textField = UITextField(frame: CGRect(x: 10, y: 200, width: (Int(self.view.frame.width) - 20), height: 45))
textField.borderStyle = .bezel
textField.delegate = self
self.view.addSubview(textField)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
initView()
}
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
let keyBoardView = MyKeyBoardView(frame: CGRect(x: 0, y: self.view.frame.height - 277, width: self.view.frame.width, height: 277))
mKeyBoardView = keyBoardView
keyBoardView.delegate = self
tempString = keyBoardView.tempString
keyBoardView.becomeFirstResponder()
self.textField.inputView = keyBoardView
keyBoardView.show()
return true
}
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
textField.text = tempString
return true
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
mKeyBoardView.resignFirstResponder()
mKeyBoardView.dismissView()
return true
}
}
效果:
未弹出键盘时:
弹出后点击了部分按键:
点击了删除按键: