iOS开发使用UIKeyInput自定义密码输入框
<h1>前言</h1><p>开发中很多地方都会遇到密码输入,这时候往往需要根据UI设计自定义。这里遵守UIKeyInput,实现协议中的方法,让自定义View可以进行文字输入;再通过func draw(_ rect: CGRect)绘制现自定义UI;使用配置类来统一接口;使用代理来管理各种输入相关的事件。文章末尾有提供OC和Swift双语的CLDemo下载,这里讲解就使用Swift。</p>
<p><strong>作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:812157648,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!</strong></p>
<h2>1.遵守UIKeyInput协议,实现文字输入</h2>
<p>遵守UIKeyInput协议,实现协议中- (BOOL)hasText、- (void)insertText:(NSString *)text、- (void)deleteBackward这三个方法。这里方便阅读,单独抽离成为一个extension。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">extension CLPasswordInputView: UIKeyInput {
</span><span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)"> hasText: Bool {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> text.length > <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">
}
func insertText(_ text: String) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> self.text.length <<span style="color: rgba(0, 0, 0, 1)"> config.passwordNum {
let cs </span>= NSCharacterSet.init(charactersIn: <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">0123456789</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">).inverted
let </span><span style="color: rgba(0, 0, 255, 1)">string</span> = text.components(separatedBy: cs).joined(separator: <span style="color: rgba(128, 0, 0, 1)">""</span><span style="color: rgba(0, 0, 0, 1)">)
let basicTest </span>= text == <span style="color: rgba(0, 0, 255, 1)">string</span>
<span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> basicTest {
self.text.append(text)
</span><span style="color: rgba(0, 0, 255, 1)">delegate</span>?<span style="color: rgba(0, 0, 0, 1)">.passwordInputViewDidChange(passwordInputView: self)
</span><span style="color: rgba(0, 0, 255, 1)">if</span> self.text.length ==<span style="color: rgba(0, 0, 0, 1)"> config.passwordNum {
</span><span style="color: rgba(0, 0, 255, 1)">delegate</span>?<span style="color: rgba(0, 0, 0, 1)">.passwordInputViewCompleteInput(passwordInputView: self)
}
setNeedsDisplay()
}
}
}
func deleteBackward() {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> text.length > <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)"> {
text.deleteCharacters(</span><span style="color: rgba(0, 0, 255, 1)">in</span>: NSRange(location: text.length - <span style="color: rgba(128, 0, 128, 1)">1</span>, length: <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">))
</span><span style="color: rgba(0, 0, 255, 1)">delegate</span>?<span style="color: rgba(0, 0, 0, 1)">.passwordInputViewDidChange(passwordInputView: self)
}
</span><span style="color: rgba(0, 0, 255, 1)">delegate</span>?<span style="color: rgba(0, 0, 0, 1)">.passwordInputViewDidDeleteBackward(passwordInputView: self)
setNeedsDisplay()
}
}</span></pre>
</div>
<h2>2.重写override func draw(_ rect: CGRect),绘制自定义UI</h2>
<p>根据配置信息,以及当前文字输入,绘制自定义UI,这里讲绘制代码和一些基本代码写在一起,单独抽离成extension。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">extension CLPasswordInputView {
</span><span style="color: rgba(0, 0, 255, 1)">override</span> func becomeFirstResponder() -><span style="color: rgba(0, 0, 0, 1)"> Bool {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> !<span style="color: rgba(0, 0, 0, 1)">isShow {
</span><span style="color: rgba(0, 0, 255, 1)">delegate</span>?<span style="color: rgba(0, 0, 0, 1)">.passwordInputViewBeginInput(passwordInputView: self)
}
isShow </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> super.becomeFirstResponder()
}
</span><span style="color: rgba(0, 0, 255, 1)">override</span> func resignFirstResponder() -><span style="color: rgba(0, 0, 0, 1)"> Bool {
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> isShow {
</span><span style="color: rgba(0, 0, 255, 1)">delegate</span>?<span style="color: rgba(0, 0, 0, 1)">.passwordInputViewEndInput(passwordInputView: self)
}
isShow </span>= <span style="color: rgba(0, 0, 255, 1)">false</span>
<span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> super.resignFirstResponder()
}
</span><span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)"> keyboardType: UIKeyboardType {
</span><span style="color: rgba(0, 0, 255, 1)">get</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> .numberPad
}
</span><span style="color: rgba(0, 0, 255, 1)">set</span><span style="color: rgba(0, 0, 0, 1)"> {
}
}
</span><span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)"> canBecomeFirstResponder: Bool {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
}
</span><span style="color: rgba(0, 0, 255, 1)">override</span> <span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)"> canResignFirstResponder: Bool {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">
}
</span><span style="color: rgba(0, 0, 255, 1)">override</span> func touchesBegan(_ touches: Set<UITouch>, with <span style="color: rgba(0, 0, 255, 1)">event</span>: UIEvent?<span style="color: rgba(0, 0, 0, 1)">) {
super.touchesBegan(touches, with: </span><span style="color: rgba(0, 0, 255, 1)">event</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">if</span> !<span style="color: rgba(0, 0, 0, 1)">isFirstResponder {
_ </span>=<span style="color: rgba(0, 0, 0, 1)"> becomeFirstResponder()
}
}
func updateWithConfig(config: ((CLPasswordInputViewConfigure) </span>-> Void)?) -><span style="color: rgba(0, 0, 0, 1)"> Void {
config</span>?<span style="color: rgba(0, 0, 0, 1)">(self.config)
backgroundColor </span>=<span style="color: rgba(0, 0, 0, 1)"> self.config.backgroundColor
setNeedsDisplay()
}
</span><span style="color: rgba(0, 0, 255, 1)">override</span><span style="color: rgba(0, 0, 0, 1)"> func layoutSubviews() {
super.layoutSubviews()
setNeedsDisplay()
}
</span><span style="color: rgba(0, 0, 255, 1)">override</span><span style="color: rgba(0, 0, 0, 1)"> func draw(_ rect: CGRect) {
let height </span>=<span style="color: rgba(0, 0, 0, 1)"> rect.size.height
let width </span>=<span style="color: rgba(0, 0, 0, 1)"> rect.size.width
let squareWidth </span>= min(max(min(height, config.squareWidth), config.pointRadius * <span style="color: rgba(128, 0, 128, 1)">4</span><span style="color: rgba(0, 0, 0, 1)">), height)
let pointRadius </span>= min(config.pointRadius, squareWidth * <span style="color: rgba(128, 0, 128, 1)">0.5</span>) * <span style="color: rgba(128, 0, 128, 1)">0.8</span><span style="color: rgba(0, 0, 0, 1)">
let middleSpace </span>= CGFloat(width - CGFloat(config.passwordNum) * squareWidth) / CGFloat(CGFloat(config.passwordNum - <span style="color: rgba(128, 0, 128, 1)">1</span>) + config.spaceMultiple * <span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">)
let leftSpace </span>= middleSpace *<span style="color: rgba(0, 0, 0, 1)"> config.spaceMultiple
let y </span>= (height - squareWidth) * <span style="color: rgba(128, 0, 128, 1)">0.5</span><span style="color: rgba(0, 0, 0, 1)">
let context </span>=<span style="color: rgba(0, 0, 0, 1)"> UIGraphicsGetCurrentContext()
</span><span style="color: rgba(0, 0, 255, 1)">for</span> i <span style="color: rgba(0, 0, 255, 1)">in</span> <span style="color: rgba(128, 0, 128, 1)">0</span> ..<<span style="color: rgba(0, 0, 0, 1)"> config.passwordNum {
context</span>?.addRect(CGRect(x: leftSpace + CGFloat(i) * squareWidth + CGFloat(i) *<span style="color: rgba(0, 0, 0, 1)"> middleSpace, y: y, width: squareWidth, height: squareWidth))
context</span>?.setLineWidth(<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">)
context</span>?<span style="color: rgba(0, 0, 0, 1)">.setStrokeColor(config.rectColor.cgColor)
context</span>?<span style="color: rgba(0, 0, 0, 1)">.setFillColor(config.rectBackgroundColor.cgColor)
}
context</span>?.drawPath(<span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)">: .fillStroke)
context</span>?<span style="color: rgba(0, 0, 0, 1)">.setFillColor(config.pointColor.cgColor)
</span><span style="color: rgba(0, 0, 255, 1)">for</span> i <span style="color: rgba(0, 0, 255, 1)">in</span> <span style="color: rgba(128, 0, 128, 1)">0</span> ..<<span style="color: rgba(0, 0, 0, 1)"> text.length {
context</span>?.addArc(center: CGPoint(x: leftSpace + CGFloat(i + <span style="color: rgba(128, 0, 128, 1)">1</span>) * squareWidth + CGFloat(i) * middleSpace - squareWidth * <span style="color: rgba(128, 0, 128, 1)">0.5</span>, y: y + squareWidth * <span style="color: rgba(128, 0, 128, 1)">0.5</span>), radius: pointRadius, startAngle: <span style="color: rgba(128, 0, 128, 1)">0</span>, endAngle: .pi * <span style="color: rgba(128, 0, 128, 1)">2</span>, clockwise: <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">)
context</span>?.drawPath(<span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)">: .fill)
}
}
}</span></pre>
</div>
<h2>3.使用配置类,来统一接口,生成基本配置信息</h2>
<p>自定义UI过程中,对于颜色,间隙,原点大小等,都需要留出接口,方便外部修改。一大堆属性,对于使用者而言,并不友好,因为他并不知道哪些属性是必须的,哪些是非必须的,为了让使用者方便使用,这里单独抽离出一个配置信息类,在内部实现基础配置,同时给出方法,让外部可以修改某些属性。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> CLPasswordInputViewConfigure: NSObject {
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">密码的位数</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> passwordNum: UInt = <span style="color: rgba(128, 0, 128, 1)">6</span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">边框正方形的大小</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> squareWidth: CGFloat = <span style="color: rgba(128, 0, 128, 1)">50</span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">黑点的半径</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> pointRadius: CGFloat = <span style="color: rgba(128, 0, 128, 1)">18</span> * <span style="color: rgba(128, 0, 128, 1)">0.5</span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">边距相对中间间隙倍数</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> spaceMultiple: CGFloat = <span style="color: rgba(128, 0, 128, 1)">5</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">黑点颜色</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> pointColor: UIColor =<span style="color: rgba(0, 0, 0, 1)"> UIColor.black
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">边框颜色</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> rectColor: UIColor =<span style="color: rgba(0, 0, 0, 1)"> UIColor.lightGray
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">输入框背景颜色</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> rectBackgroundColor: UIColor =<span style="color: rgba(0, 0, 0, 1)"> UIColor.white
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">控件背景颜色</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> backgroundColor: UIColor =<span style="color: rgba(0, 0, 0, 1)"> UIColor.white
</span><span style="color: rgba(0, 0, 255, 1)">class</span> func defaultConfig() -><span style="color: rgba(0, 0, 0, 1)"> CLPasswordInputViewConfigure {
let configure </span>=<span style="color: rgba(0, 0, 0, 1)"> CLPasswordInputViewConfigure()
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> configure
}
}</span></pre>
</div>
<p>外部修改配置的方法,使用闭包,将基本配置回调到外部,同时在外部修改这些属性后,对内部UI进行刷新,这里bloick是局部变量,不会循环引用。</p>
<div class="cnblogs_code">
<pre>func updateWithConfig(config: ((CLPasswordInputViewConfigure) -> Void)?) -><span style="color: rgba(0, 0, 0, 1)"> Void {
config</span>?<span style="color: rgba(0, 0, 0, 1)">(self.config)
backgroundColor </span>=<span style="color: rgba(0, 0, 0, 1)"> self.config.backgroundColor
setNeedsDisplay()
}</span></pre>
</div>
<h2>4.使用代理来管理各种输入相关的事件</h2>
<p>这里单独创建一个协议,管理各种输入事件,同时通过extension实现这些协议,这样外部就可以选择性的实现这些协议,而不是必须实现。</p>
<div class="cnblogs_code">
<pre>protocol CLPasswordInputViewDelegate: <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">输入改变</span>
func passwordInputViewDidChange(passwordInputView:CLPasswordInputView) -><span style="color: rgba(0, 0, 0, 1)"> Void
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">点击删除</span>
func passwordInputViewDidDeleteBackward(passwordInputView:CLPasswordInputView) -><span style="color: rgba(0, 0, 0, 1)"> Void
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">输入完成</span>
func passwordInputViewCompleteInput(passwordInputView:CLPasswordInputView) -><span style="color: rgba(0, 0, 0, 1)"> Void
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">开始输入</span>
func passwordInputViewBeginInput(passwordInputView:CLPasswordInputView) -><span style="color: rgba(0, 0, 0, 1)"> Void
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">结束输入</span>
func passwordInputViewEndInput(passwordInputView:CLPasswordInputView) -><span style="color: rgba(0, 0, 0, 1)"> Void
}
extension CLPasswordInputViewDelegate {
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">输入改变</span>
func passwordInputViewDidChange(passwordInputView:CLPasswordInputView) -><span style="color: rgba(0, 0, 0, 1)"> Void {
}
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">点击删除</span>
func passwordInputViewDidDeleteBackward(passwordInputView:CLPasswordInputView) -><span style="color: rgba(0, 0, 0, 1)"> Void {
}
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">输入完成</span>
func passwordInputViewCompleteInput(passwordInputView:CLPasswordInputView) -><span style="color: rgba(0, 0, 0, 1)"> Void {
}
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">开始输入</span>
func passwordInputViewBeginInput(passwordInputView:CLPasswordInputView) -><span style="color: rgba(0, 0, 0, 1)"> Void {
}
</span><span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)">结束输入</span>
func passwordInputViewEndInput(passwordInputView:CLPasswordInputView) -><span style="color: rgba(0, 0, 0, 1)"> Void {
}
}</span></pre>
</div>
<h2>5.效果图</h2>
<p>这里简单录制了一个效果,更多请参考CLDemo</p>
<p><img src="https://img2020.cnblogs.com/blog/2240560/202012/2240560-20201211211709056-1849175951.png"></p>
<p> </p>
<p> </p>
<h2>6.总结</h2>
<p>为了方便大家学习,这里提供了OC和Swift两种语言分别实现的----->>>CLDemo,如果对你有所帮助,欢迎Star。</p>
<p>原文作者:季末微夏<br>原文地址:https://www.jianshu.com/p/7305321e10b1</p><br><br>
来源:https://www.cnblogs.com/fadaijun/p/14122762.html
頁:
[1]