方格验证码输入框实现方式

引言

在实际开发过程中验证码输入框是一个很常见UI界面。通常来讲有简单的输入框,也有方格的输入框,其中相对较为棘手就是这种方格输入框里面还需要显示光标的情况。本篇博客我们就来主要讨论一下方格带光标的验证码输入框样式。

实现方案

在着手实现这个功能之前,首先我们需要明确需要做的事情:

  1. 创建出方格,并且每个方格都需要响应用户输入。
  2. 在方格输入一个数字后,需要自动切换到下一个方格响应输入。
  3. 在方格删除一个数字后,需要自动切换到上一个方格响应。
  4. 所有方格都输入完成之后,需要通知页面控制器。

1.创建方格

既然每个方格都需要响应用户的输入(且显示光标),那我们很容易就会想到方格需要有UITextFiled用来响应用户输入和光标。当切换到下一个方格时,上一个方格还需要显示已经输入的内容,这里我们采用一个UILabel来实现。

这样我们自定义一个单元输入框大概结构就有了,代码如下:

class PHInputItemView: UIView, UITextFieldDelegate {
    
    /// 显示label
    private let label = UILabel()
    /// 实际输入框
    private let codeField = PHCodeField()
   
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
        setLayout()
        setEvent()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setupView() {
        self.layer.masksToBounds = true
        self.layer.cornerRadius = 8.0
        self.layer.borderWidth = 1.0
        self.layer.borderColor = UIColor.clear.cgColor
        
        self.addSubview(codeField)
        codeField.font = UIFont.systemFont(ofSize: 30, weight: .medium)
        codeField.textAlignment = .center
        codeField.textColor = .black
        codeField.tintColor = .black
        codeField.backgroundColor = .clear
        
        self.addSubview(label)
        label.font = UIFont.systemFont(ofSize: 30, weight: .medium)
        label.textAlignment = .center
        label.textColor = .black
        label.backgroundColor = .black.withAlphaComponent(0.4)
        
    }
    
    func setLayout() {
        label.frame = self.bounds
        codeField.frame = self.bounds
    }
    
    func setEvent() {
       
    }

    
    override func layoutSubviews() {
        super.layoutSubviews()
        setLayout()
    }
    
}

由于我们需要在用户输入或者删除时进行一些切换的操作,所以这个自定义的单元输入需要将事件回调出去,这里我们采用了闭包的形式进行回调,操作如下:

用户输入

增加输入回调闭包:

    /// 输入回调
    var inputAction: ((String) -> Void)?

设置代理:

        codeField.delegate = self

实现代理方法:

    //MARK: UITextFieldDelegate
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        if string.count == 1 {
            self.label.text = string
            self.inputAction?(string)
            return false
        }
        return true
    }

用户删除

用户删除,我们使用代理方法或者是监听通知时,如果输入框内本来就没有内容是没有获取到回调的,因此我们采用了重写UITextFiled的方式来获取删除按钮的点击事件。

代码如下:

import UIKit

class PHCodeField: UITextField {
    
    /// 键盘删除按钮点击回调
    var deleteAction: (() -> Void)?
    
    override func deleteBackward() {
        super.deleteBackward()
        deleteAction?()
    }
}

增加删除回调:

    /// 删除回调
    var deleteAction: (() -> Void)?

调用删除回调:

    func setEvent() {
        codeField.deleteAction = { [weak self] in
            guard let self = self else { return }
            self.deleteAction?()
        }
    }

自定义单元输入框状态

另外为了区分输入框是否处于响应状态我们采用不同的UI样式来进行区分一下,代码如下:

    /// 选中属性
    var isSelected:Bool = false {
        didSet {
            if isSelected {
                self.layer.borderColor = UIColor.orange.cgColor
                codeField.becomeFirstResponder()
                codeField.isHidden = false
                label.isHidden = true
            } else {
                self.layer.borderColor = UIColor.clear.cgColor
                codeField.resignFirstResponder()
                codeField.isHidden = true
                label.isHidden = false
            }
        }
    }

获取验证码

添加一个只读属性,读取验证码的值。

/// 验证码
    var code:String? {
        get {
            return label.text
        }
    }

2.创建验证码输入视图

这个相对简单一些,我们只需要根据验证码的个数将刚刚创建的最小单元输入口整齐的排在上面就可以了,案例中我们设置输入框的个数为5,间距为8,size为{40,54}。

class PHVerifyCodeView: UIView {
    
    /// 验证码数字个数
    private var count = 5
    /// 默认间距
    private var space:CGFloat = 8.0
    /// stackView
    private let contentView = UIView()
    /// 默认size
    private var itemSize:CGSize = CGSize(width: 40.0, height: 54.0)

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
        setLayout()
        addInputItemViews()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setupView() {
        self.addSubview(contentView)
        
    }
    
    func setLayout() {
        contentView.frame = self.bounds
    }
    
    func addInputItemViews()  {
        var offsetX = (contentView.bounds.size.width - CGFloat(count) * itemSize.width - CGFloat(count - 1) * space) / 2.0
        for i in 0 ..< count {
            let itemView = PHInputItemView(frame: CGRect(x: offsetX, y: 0.0, width: itemSize.width, height: itemSize.height))
            contentView.addSubview(itemView)
            if i == 0 {
                itemView.isSelected = true
            }
            offsetX += itemSize.width + space
        }
    }
 
    
    override func layoutSubviews() {
        super.layoutSubviews()
        setLayout()
    }
}

3.响应用户输入,自动切换到下一个单元输入框

响应用户的输入回调,代码如下:

            itemView.inputAction = { [weak self] text in
                guard let self = self else { return }
                self.input(text: text, index: i, itemView: itemView)
            }

实现自动切换到下一个输入框,这里面我们需要注意到临界值:

    private func input(text:String,index:Int,itemView:PHInputItemView) {
        if index == count - 1 {
            itemView.isSelected = false
        } else {
            let nextItemView = contentView.subviews[index + 1] as! PHInputItemView
            nextItemView.isSelected = true
            itemView.isSelected = false
        }
    }

4.相应用户删除,自动切换到上一个输入框

响应用户删除,代码如下:

            itemView.deleteAction = { [weak self] in
                guard let self = self else { return }
                self.delete(index: i, itemView: itemView)
            }

实现自动切换到上一个输入框,代码如下:

    private func delete(index:Int,itemView:PHInputItemView) {
        if index == 0 {
            return
        }
        let preItemView = contentView.subviews[index - 1] as! PHInputItemView
        preItemView.isSelected = true
        itemView.isSelected = false
    }

5.输入完成回调

添加输入完成回调:

/// 输入完成回调
    var inputFinish: ((String) -> Void)?
    

在用户输入完成之后,调用回调函数传入验证码:

    private func input(text:String,index:Int,itemView:PHInputItemView) {
        if index == count - 1 {
            itemView.isSelected = false
            inputFinish?(getVerifyCode())
        } else {
            let nextItemView = contentView.subviews[index + 1] as! PHInputItemView
            nextItemView.isSelected = true
            itemView.isSelected = false
        }
    }
    
    /// 获取验证码
    func getVerifyCode() -> String {
        var code = ""
        for view in contentView.subviews {
            let itemView = view as! PHInputItemView
            code += itemView.code ?? ""
        }
        return code
    }

6.使用验证码输入框

在视图控制器中直接使用,代码如下:

        let verifyCodeView = PHVerifyCodeView(frame: CGRect(x: 0.0, y: 100.0, width: self.view.bounds.size.width, height: 54.0))
        verifyCodeView.backgroundColor = .cyan
        self.view.addSubview(verifyCodeView)

实现输入完成的回调:

  verifyCodeView.inputFinish = { code in
            print("输入完成:\(code)")
        }

结语

实现一个带有光标的方块输入框就完成了,如果不需要显示光标,那么实现起来会非常简单。我们只需创建几个 UILabel和一个隐藏的 UITextField,然后监听用户输入,将所有内容分割并按顺序显示到 UILabel 中即可。

每个需求和设计的实现方案都不是唯一的,如果大家有更好的方案,欢迎分享出来。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/792014.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

顺序结构 ( 六 ) —— 顺序结构实例 【互三互三】

&#x1f680;欢迎互三&#x1f449;&#xff1a;程序猿方梓燚 &#x1f48e;&#x1f48e;&#x1f680;所属专栏&#xff1a;C教程&#x1f48e; &#x1f680;关注博主&#xff0c;后期持续更新系列文章 &#x1f680;如果有错误感谢请大家批评指出&#xff0c;及时修改 &am…

什么是RLHF(基于人类反馈的强化学习)?

什么是RLHF&#xff08;基于人类反馈的强化学习&#xff09;&#xff1f; 基于人类反馈的强化学习&#xff08;Reinforcement Learning from Human Feedback, RLHF&#xff09;是一种结合强化学习和人类反馈的技术&#xff0c;用于训练智能体&#xff0c;使其行为更符合人类期…

农牧行业CRM洞察:打造营、销、服一体化数字营销平台

01、行业应用背景 保持企业活力&#xff0c;支撑业务单元协调发展&#xff0c;稳定核心产品竞争力&#xff0c;将成为农牧行业企业数字化、数智化建设的指导方向。 积极发挥数据在生产、流通、消费各个环节的决策支撑&#xff0c;为农牧企业特别是多业态集团型企业&#xff0…

1.浅谈蓝牙BLE的总体框架

这里只展开BLE这一部分&#xff0c; 框图如下所示 蓝牙也是使用分层的结构组织代码。 Application&#xff1a;是自己的业务逻辑实现的地方。当然应用程序需要根据BLE的规定&#xff0c;实现配置文件&#xff08;profile&#xff09;、服务&#xff08;service&#xff09;和…

【话题】开源项目:从边缘到主流的转变之旅

目录 开源项目有哪些机遇与挑战&#xff1f; 前言 宏观视角&#xff1a;开源项目的发展趋势 开源运动&#xff0c;作为一股不可忽视的创新力量&#xff0c;正在重塑全球科技版图。其核心价值在于打破知识的壁垒&#xff0c;推动技术的民主化&#xff0c;让信息与技术不再为少…

C++ :内联函数inline|nullptr

欢迎来到HarperLee的学习笔记&#xff01; 博主主页传送门&#xff1a;HarperLee博客主页&#xff01; 欢迎交流学习&#xff01; 一、inline关键字 1.1 什么是内联函数&#xff1f; 内联函数&#xff1a;用** inline 修饰的函数叫做内联函数&#xff0c;编译时C编译器会在调用…

PostgreSQL行级安全策略探究

前言 最近和朋友讨论oracle行级安全策略(VPD)时&#xff0c;查看了下官方文档&#xff0c;看起来VPD的原理是针对应用了Oracle行级安全策略的表、视图或同义词发出的 SQL 语句动态添加where子句。通俗理解就是将行级安全策略动态添加为where 条件。那么PG中的行级安全策略是怎…

R包:‘ggcharts好看线图包‘

介绍 ggcharts提供了一个高级{ggplot2}接口&#xff0c;用于创建通用图表。它的目标既简单又雄心勃勃:让您更快地从数据可视化的想法到实际的绘图。所以如何?通过处理大量的数据预处理&#xff0c;为您模糊{ggplot2}细节和绘图样式。生成的图是ggplot对象&#xff0c;可以使用…

CTF php RCE(三)

0x07 日志文件包含 判断类型 使用kali curl -I urlF12 打开F12开发者工具&#xff0c;选中之后F5刷新查看server类型即可 配置文件 直接包含或者访问如果有回显就是&#xff0c; NGINX&#xff1a;NGINX 的配置文件通常位于 /etc/nginx/ 目录下&#xff0c;具体的网站配…

【深度学习入门篇 ④ 】Pytorch实现手写数字识别

【&#x1f34a;易编橙&#xff1a;一个帮助编程小伙伴少走弯路的终身成长社群&#x1f34a;】 大家好&#xff0c;我是小森( &#xfe61;ˆoˆ&#xfe61; ) &#xff01; 易编橙终身成长社群创始团队嘉宾&#xff0c;橙似锦计划领衔成员、阿里云专家博主、腾讯云内容共创官…

LLMs可以进行任务规划吗?如果不行,LLMs+GNN可以吗?

深度图学习与大模型LLM(小编): 大家好,今天向大家介绍一篇最新发布的研究论文&#xff08;20240530&#xff09;。这篇论文探讨了如何通过引入GNN来提高大模型在任务规划(task planning)中的性能。*论文分析了LLMs在任务规划上的局限性,并提出了一种简单而有效的解决方案。* 1.…

VIM模式之间的切换

命令行界面下&#xff0c;常用的文本编辑器是 VI / VIM(VI增强版)&#xff0c;VI 是 Linux 最通用的文本编辑器&#xff0c;VIM相较于VI&#xff0c;提供了代码高亮等功能&#xff0c;两者用法完全兼容&#xff1b; 1. 进入 VIM 工作界面 vim 文件名 2. 进入编辑模式 三种方…

深入分析与解决4.3问题:iOS应用版本更新审核被拒原因解析

深入分析与解决4.3问题&#xff1a;iOS应用版本更新审核被拒原因解析 在iOS应用开发和发布过程中&#xff0c;遇到4.3问题&#xff08;设计 - 垃圾邮件&#xff09;是一个常见且令人头疼的情况。即使您的应用已成功发布其第一个版本&#xff0c;但在进行版本更新时&#xff0c…

【React Hooks原理 - useState】

概述 useState赋予了Function Component状态管理的能力&#xff0c;可以让你在不编写 class 的情况下使用 state 。其本质上就是一类特殊的函数&#xff0c;它们约定以 use 开头。本文从源码出发&#xff0c;一步一步看看useState是如何实现以及工作的。 基础使用 function …

数据结构day6链式队列

主程序 #include "fun.h" int main(int argc, const char *argv[]) { que_p Qcreate(); enqueue(Q,10); enqueue(Q,20); enqueue(Q,30); enqueue(Q,40); enqueue(Q,50); show_que(Q); dequeue(Q); show_que(Q); printf(&qu…

小程序复制功能不可用 setClipboardData:fail no permission

先上图 用户协议剪切板也更新但是依旧报错了 最后在公众平台通知里发现是用户之前小程序有规格被封禁了该功能

【常见开源库的二次开发】基于openssl的加密与解密——openssl认识与配置(一)

目录&#xff1a; 目录&#xff1a; 一、什么是openssl&#xff1f; 二、所需要具备的开发工具 三、Windows上编译OpenSSL3.0 四、Linux编译openssl3.0 一、什么是openssl&#xff1f; OpenSSL 是一个开源的软件库&#xff0c;它提供了一系列加密工具和协议&#xff0c;主要用…

apple watch程序出错 Cannot launch apps while in nightstand mode

开发的时候运行apple watch程序出错&#xff1a; ailure Reason: The request was denied by service delegate (IOSSHLMainWorkspace) for reason: Busy ("Cannot launch apps while in nightstand mode"). 这是因为&#xff1a; 将Apple Watch放在充电器上并直立…

Python 处理文件的读写操作

Python 提供了丰富的文件读写操作&#xff0c;可以轻松处理文本文件和二进制文件。以下是关于 Python 文件读写操作的详细讲解&#xff0c;包括打开文件、读取文件、写入文件、文件指针操作、文件关闭和异常处理等方面。 一、文件的打开和关闭 在对文件进行读写操作之前&…

喜讯|华院计算法律大模型入围《2024大模型典型示范应用案例集》

2024年世界人工智能大会&#xff08;WAIC&#xff09;举办期间&#xff0c;中国信通院正式发布了《2024大模型典型示范应用案例集》&#xff08;以下简称《案例集》&#xff09;。该案例集由中国信通院华东分院、上海人工智能实验室主导&#xff0c;以产业化为导向&#xff0c;…