preface

Password entry occurs in many areas of development and is often customized according to the UI design. UIKeyInput, which implements methods in the protocol, allows custom views to do text input; Then use func draw(_ rect: CGRect) to draw the custom UI; Use configuration classes to unify interfaces; Use proxies to manage various input-related events. At the end of the article, CLDemo in both OC and Swift is available for download. Swift is used for this explanation.

1. Comply with theUIKeyInputProtocol, the realization of text input

Comply with UIKeyInput protocol, and implement the three methods – (BOOL)hasText, – (void)insertText:(NSString *)text, – (void)deleteBackward in the protocol. Easy to read here, separate out into an extension.

extension CLPasswordInputView: UIKeyInput {
    var hasText: Bool {
        return text.length > 0
    }
    
    func insertText(_ text: String) {
        if self.text.length < config.passwordNum {
            let cs = NSCharacterSet.init(charactersIn: "0123456789").inverted
            let string = text.components(separatedBy: cs).joined(separator: "")
            let basicTest = text == string
            ifbasicTest { self.text.append(text) delegate? .passwordInputViewDidChange(passwordInputView: self)ifself.text.length == config.passwordNum { delegate? .passwordInputViewCompleteInput(passwordInputView: self) }setNeedsDisplay()
            }
        }
    }
    
    func deleteBackward() {
        if text.length > 0 {
            text.deleteCharacters(in: NSRange(location: text.length - 1, length: 1)) delegate? .passwordInputViewDidChange(passwordInputView: self) } delegate? .passwordInputViewDidDeleteBackward(passwordInputView: self)setNeedsDisplay()
    }
}
Copy the code

2. Rewrite theoverride func draw(_ rect: CGRect)To draw a custom UI

According to the configuration information and the current text input, draw the custom UI. Here, draw the code and some basic code together, separate into extension.

extension CLPasswordInputView {
    override func becomeFirstResponder() -> Bool {
        if! isShow { delegate? .passwordInputViewBeginInput(passwordInputView: self) } isShow =true;
        return super.becomeFirstResponder()
    }
    override func resignFirstResponder() -> Bool {
        ifisShow { delegate? .passwordInputViewEndInput(passwordInputView: self) } isShow =false
        return super.resignFirstResponder()
    }
    var keyboardType: UIKeyboardType {
        get {
            return .numberPad
        }
        set {
            
        }
    }
    override var canBecomeFirstResponder: Bool {
        return true
    }
    override var canResignFirstResponder: Bool {
        return true
    }
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        if! isFirstResponder { _ = becomeFirstResponder() } } func updateWithConfig(config: ((CLPasswordInputViewConfigure) -> Void)?) -> Void { config? (self.config) backgroundColor = self.config.backgroundColorsetNeedsDisplay()
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        setNeedsDisplay()
    }
    override func draw(_ rect: CGRect) {
        let height = rect.size.height
        let width = rect.size.width
        let squareWidth = min(max(min(height, config.squareWidth), config.pointRadius * 4), height)
        letPointRadius = min(config.pointRadius, squareWidth * 0.5) * 0.8let middleSpace = CGFloat(width - CGFloat(config.passwordNum) * squareWidth) / CGFloat(CGFloat(config.passwordNum - 1) + config.spaceMultiple * 2)
        let leftSpace = middleSpace * config.spaceMultiple
        letY = (height-squareWidth) * 0.5let context = UIGraphicsGetCurrentContext()
        
        for i in0.. < config.passwordNum { context? .addRect(CGRect(x: leftSpace + CGFloat(i) * squareWidth + CGFloat(i) * middleSpace, y: y, width: squareWidth, height: squareWidth)) context? .setLineWidth(1) context? .setStrokeColor(config.rectColor.cgColor) context? .setFillColor(config.rectBackgroundColor.cgColor) } context? .drawPath(using: .fillStroke) context? .setFillColor(config.pointColor.cgColor)for i in0.. < text.length { context? .addarc (center: CGPoint(x: leftSpace + CGFloat(I + 1) * squareWidth + CGFloat(I) * middleSpace - squareWidth * 0.5, y: Y + squareWidth * 0.5), RADIUS: pointRadius, startAngle: 0, endAngle:. PI * 2, squareWidth * 5)true) context? .drawPath(using: .fill) } } }Copy the code

3. Use configuration classes to unify interfaces and generate basic configuration information

In the process of customizing UI, you need to set aside interfaces for colors, gaps, and origin sizes to facilitate external modification. A large number of attributes are not friendly to users, because they do not know which attributes are necessary and which are not necessary. In order to facilitate the use of users, a configuration information class is isolated here, and the basic configuration is implemented internally, while methods are provided for external modification of some attributes.

Class CLPasswordInputViewConfigure: NSObject {/ / / password digits var passwordNum: UInt = 6 / / / frame the size of the square var squareWidth: Var pointRadius: CGFloat = 18 * 0.5 var spaceMultiple: CGFloat = 5; Var pointColor: UIColor = uicolor.black UIColor = uicolor. lightGray var rectBackgroundColor: UIColor = uicolor. white UIColor = UIColor.white class func defaultConfig() -> CLPasswordInputViewConfigure {let configure = CLPasswordInputViewConfigure()
        return configure
    }
}
Copy the code

The method of external modification of the configuration, using closures, calls back the basic configuration to the outside, and at the same time, after the external modification of these properties, the internal UI is refreshed, where the block is a local variable, not a circular reference.

func updateWithConfig(config: ((CLPasswordInputViewConfigure) -> Void)?) -> Void { config? (self.config) backgroundColor = self.config.backgroundColorsetNeedsDisplay()
    }
Copy the code

4. Use proxies to manage various input-related events

This creates a separate protocol that manages the various input events and implements these protocols through Extension so that the external implementation of these protocols is optional, rather than mandatory.

protocol CLPasswordInputViewDelegate: The class {/ / / input change func passwordInputViewDidChange (passwordInputView: CLPasswordInputView) - > Void / / / click delete func PasswordInputViewDidDeleteBackward (passwordInputView: CLPasswordInputView) - > Void / / / input to complete the func PasswordInputViewCompleteInput (passwordInputView: CLPasswordInputView) - > Void / / / input func PasswordInputViewBeginInput (passwordInputView: CLPasswordInputView) - > Void / / / the end of the input func passwordInputViewEndInput(passwordInputView:CLPasswordInputView) -> Void } extension CLPasswordInputViewDelegate { / / / input change func passwordInputViewDidChange (passwordInputView: CLPasswordInputView) - > Void {} / / / click delete func PasswordInputViewDidDeleteBackward (passwordInputView: CLPasswordInputView) - > Void {} / / / input to complete the func PasswordInputViewCompleteInput (passwordInputView: CLPasswordInputView) - > Void {} / / / input func PasswordInputViewBeginInput (passwordInputView: CLPasswordInputView) - > Void {} / / / the end of the input func passwordInputViewEndInput(passwordInputView:CLPasswordInputView) -> Void { } }Copy the code

5. Rendering

Here is a simple recording of an effect, see CLDemo for more

6. Summary

In order to facilitate everyone to learn, here provides OC and Swift two languages respectively implemented —–>>>CLDemo, if you are helpful, welcome Star.