龙吞天下 發表於 2020-12-11 21:18:00

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 &gt; <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 &lt;<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 &gt; <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() -&gt;<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() -&gt;<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&lt;UITouch&gt;, 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>-&gt; Void)?) -&gt;<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> ..&lt;<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> ..&lt;<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() -&gt;<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) -&gt; Void)?) -&gt;<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) -&gt;<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) -&gt;<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) -&gt;<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) -&gt;<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) -&gt;<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) -&gt;<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) -&gt;<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) -&gt;<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) -&gt;<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) -&gt;<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>&nbsp;</p>
<p>&nbsp;</p>
<h2>6.总结</h2>
<p>为了方便大家学习,这里提供了OC和Swift两种语言分别实现的-----&gt;&gt;&gt;CLDemo,如果对你有所帮助,欢迎Star。</p>
<p>原文作者:季末微夏<br>原文地址:https://www.jianshu.com/p/7305321e10b1</p><br><br>
来源:https://www.cnblogs.com/fadaijun/p/14122762.html
頁: [1]
查看完整版本: iOS开发使用UIKeyInput自定义密码输入框