Swift 创建自定义键盘 (金融、聊天等领域)

前言:

某些业务领域要求使用自定义键盘而非系统内置的键盘,这时候可选择基于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
    }

}

效果:

未弹出键盘时:
未弹出键盘
弹出后点击了部分按键:
在这里插入图片描述

点击了删除按键:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SwiftUI提供了多种方法来实现自定义布局。其中,可以使用HStack、VStack、ZStack等布局容器来组合和排列视图元素,实现各种复杂界面。 另外,SwiftUI还提供了对齐指南来帮助文本对齐,如.leading、.trailing、.top等。但是当处理在不同视图之间分割的视图时,如果需要使完全不同的两个视图部分对齐,可以使用自定义的对齐辅助线。使用这些辅助线可以在整个界面中创建和使用辅助线,在视图之前或之后发生的变化并不重要,它们仍然会按照一条线排列。 例如,可以使用offset(x: CGFloat, y: CGFloat)方法为视图指定水平和垂直的偏移距离。这个方法可以将视图在水平和垂直方向上移动指定的距离。 综上所述,使用SwiftUI可以通过布局容器和对齐指南实现自定义布局,同时也可以使用偏移方法来调整视图的位置。这些功能可以帮助您创建个性化的布局。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [SwiftUI 布局之自定义手动布局设置各种相对位置(教程含源码)](https://blog.csdn.net/iCloudEnd/article/details/107718464)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [SwiftUI之深入解析布局如何自定义AlignmentGuides](https://blog.csdn.net/Forever_wj/article/details/121958698)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值