iOS開發(fā)使用UIKeyInput自定義密碼輸入框

前言

開發(fā)中很多地方都會(huì)遇到密碼輸入铺敌,這時(shí)候往往需要根據(jù)UI設(shè)計(jì)自定義踢涌。這里遵守UIKeyInput黔龟,實(shí)現(xiàn)協(xié)議中的方法,讓自定義View可以進(jìn)行文字輸入宙帝;再通過func draw(_ rect: CGRect)繪制現(xiàn)自定義UI丧凤;使用配置類來統(tǒng)一接口;使用代理來管理各種輸入相關(guān)的事件步脓。文章末尾有提供OC和Swift雙語(yǔ)的CLDemo下載,這里講解就使用Swift浩螺。

1.遵守UIKeyInput協(xié)議靴患,實(shí)現(xiàn)文字輸入

遵守UIKeyInput協(xié)議,實(shí)現(xiàn)協(xié)議中- (BOOL)hasText要出、- (void)insertText:(NSString *)text鸳君、- (void)deleteBackward這三個(gè)方法。這里方便閱讀患蹂,單獨(dú)抽離成為一個(gè)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
            if basicTest {
                self.text.append(text)
                delegate?.passwordInputViewDidChange(passwordInputView: self)
                if self.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()
    }
}

2.重寫override func draw(_ rect: CGRect)砸紊,繪制自定義UI

根據(jù)配置信息,以及當(dāng)前文字輸入囱挑,繪制自定義UI醉顽,這里講繪制代碼和一些基本代碼寫在一起,單獨(dú)抽離成extension平挑。

extension CLPasswordInputView {
    override func becomeFirstResponder() -> Bool {
        if !isShow {
            delegate?.passwordInputViewBeginInput(passwordInputView: self)
        }
        isShow = true;
        return super.becomeFirstResponder()
    }
    override func resignFirstResponder() -> Bool {
        if isShow {
            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.backgroundColor
        setNeedsDisplay()
    }
    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)
        let pointRadius = min(config.pointRadius, squareWidth * 0.5) * 0.8
        let middleSpace = CGFloat(width - CGFloat(config.passwordNum) * squareWidth) / CGFloat(CGFloat(config.passwordNum - 1) + config.spaceMultiple * 2)
        let leftSpace = middleSpace * config.spaceMultiple
        let y = (height - squareWidth) * 0.5
        
        let context = UIGraphicsGetCurrentContext()
        
        for i in 0 ..< 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 in 0 ..< 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, clockwise: true)
            context?.drawPath(using: .fill)
        }
    }
}

3.使用配置類游添,來統(tǒng)一接口,生成基本配置信息

自定義UI過程中通熄,對(duì)于顏色唆涝,間隙,原點(diǎn)大小等唇辨,都需要留出接口廊酣,方便外部修改。一大堆屬性赏枚,對(duì)于使用者而言亡驰,并不友好,因?yàn)樗⒉恢滥男傩允潜仨毜奈撕兀男┦欠潜仨毜囊猓瑸榱俗屖褂谜叻奖闶褂茫@里單獨(dú)抽離出一個(gè)配置信息類诫睬,在內(nèi)部實(shí)現(xiàn)基礎(chǔ)配置煞茫,同時(shí)給出方法,讓外部可以修改某些屬性摄凡。

class CLPasswordInputViewConfigure: NSObject {
    ///密碼的位數(shù)
    var passwordNum: UInt = 6
    ///邊框正方形的大小
    var squareWidth: CGFloat = 50
    ///黑點(diǎn)的半徑
    var pointRadius: CGFloat = 18 * 0.5
    ///邊距相對(duì)中間間隙倍數(shù)
    var spaceMultiple: CGFloat = 5;
    ///黑點(diǎn)顏色
    var pointColor: UIColor = UIColor.black
    ///邊框顏色
    var rectColor: UIColor = UIColor.lightGray
    ///輸入框背景顏色
    var rectBackgroundColor: UIColor = UIColor.white
    ///控件背景顏色
    var backgroundColor: UIColor = UIColor.white
    
    class func defaultConfig() -> CLPasswordInputViewConfigure {
        let configure = CLPasswordInputViewConfigure()
        return configure
    }
}

外部修改配置的方法续徽,使用閉包,將基本配置回調(diào)到外部亲澡,同時(shí)在外部修改這些屬性后钦扭,對(duì)內(nèi)部UI進(jìn)行刷新,這里bloick是局部變量床绪,不會(huì)循環(huán)引用客情。

func updateWithConfig(config: ((CLPasswordInputViewConfigure) -> Void)?) -> Void {
        config?(self.config)
        backgroundColor = self.config.backgroundColor
        setNeedsDisplay()
    }

4.使用代理來管理各種輸入相關(guān)的事件

這里單獨(dú)創(chuàng)建一個(gè)協(xié)議,管理各種輸入事件癞己,同時(shí)通過extension實(shí)現(xiàn)這些協(xié)議膀斋,這樣外部就可以選擇性的實(shí)現(xiàn)這些協(xié)議,而不是必須實(shí)現(xiàn)痹雅。

protocol CLPasswordInputViewDelegate: class {
    ///輸入改變
    func passwordInputViewDidChange(passwordInputView:CLPasswordInputView) -> Void
    ///點(diǎn)擊刪除
    func passwordInputViewDidDeleteBackward(passwordInputView:CLPasswordInputView) -> Void
    ///輸入完成
    func passwordInputViewCompleteInput(passwordInputView:CLPasswordInputView) -> Void
    ///開始輸入
    func passwordInputViewBeginInput(passwordInputView:CLPasswordInputView) -> Void
    ///結(jié)束輸入
    func passwordInputViewEndInput(passwordInputView:CLPasswordInputView) -> Void
}

extension CLPasswordInputViewDelegate {
    ///輸入改變
    func passwordInputViewDidChange(passwordInputView:CLPasswordInputView) -> Void {
        
    }
    ///點(diǎn)擊刪除
    func passwordInputViewDidDeleteBackward(passwordInputView:CLPasswordInputView) -> Void {
        
    }
    ///輸入完成
    func passwordInputViewCompleteInput(passwordInputView:CLPasswordInputView) -> Void {
        
    }
    ///開始輸入
    func passwordInputViewBeginInput(passwordInputView:CLPasswordInputView) -> Void {
        
    }
    ///結(jié)束輸入
    func passwordInputViewEndInput(passwordInputView:CLPasswordInputView) -> Void {
        
    }
}

5.效果圖

這里簡(jiǎn)單錄制了一個(gè)效果仰担,更多請(qǐng)參考CLDemo

效果圖.gif

6.總結(jié)

為了方便大家學(xué)習(xí),這里提供了OC和Swift兩種語(yǔ)言分別實(shí)現(xiàn)的----->>>CLDemo绩社,如果對(duì)你有所幫助摔蓝,歡迎Star赂苗。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市贮尉,隨后出現(xiàn)的幾起案子拌滋,更是在濱河造成了極大的恐慌,老刑警劉巖绘盟,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸠真,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡龄毡,警方通過查閱死者的電腦和手機(jī)吠卷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沦零,“玉大人祭隔,你說我怎么就攤上這事÷凡伲” “怎么了疾渴?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)屯仗。 經(jīng)常有香客問我搞坝,道長(zhǎng),這世上最難降的妖魔是什么魁袜? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任桩撮,我火速辦了婚禮,結(jié)果婚禮上峰弹,老公的妹妹穿的比我還像新娘店量。我一直安慰自己,他們只是感情好鞠呈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布融师。 她就那樣靜靜地躺著,像睡著了一般蚁吝。 火紅的嫁衣襯著肌膚如雪旱爆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天窘茁,我揣著相機(jī)與錄音疼鸟,去河邊找鬼。 笑死庙曙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的浩淘。 我是一名探鬼主播捌朴,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼吴攒,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了砂蔽?” 一聲冷哼從身側(cè)響起洼怔,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎左驾,沒想到半個(gè)月后镣隶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诡右,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年安岂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片帆吻。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡域那,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出猜煮,到底是詐尸還是另有隱情次员,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布王带,位于F島的核電站淑蔚,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏愕撰。R本人自食惡果不足惜刹衫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望盟戏。 院中可真熱鬧绪妹,春花似錦、人聲如沸柿究。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蝇摸。三九已至婶肩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間貌夕,已是汗流浹背律歼。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留啡专,地道東北人险毁。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親畔况。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鲸鹦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • (一) 我一直記得小學(xué)時(shí)候的一件事。 同學(xué)L是個(gè)很漂亮的女孩子跷跪,頭發(fā)偏栗色馋嗜,皮膚白皙,個(gè)子還高挑吵瞻。是那種于千萬(wàn)人之...
    淮南為橘子閱讀 1,225評(píng)論 0 0
  • “ 村上春樹說葛菇,如果我愛你,而你也正巧愛我橡羞; 你頭發(fā)亂了的時(shí)候眯停,我會(huì)笑笑地替你撥一撥。 然后尉姨,手還留戀地在你的...
    梅蘭君子芳閱讀 242評(píng)論 0 0
  • 老詹每次出汗后都感覺是nba2k里的人物
    無勇取謀閱讀 203評(píng)論 0 0
  • 跟一個(gè)結(jié)了婚在家?guī)Ш⒆拥呐笥蚜奶焘殖笥颜f,以前的時(shí)候孩子整天黏在家里還有點(diǎn)兒事情做又厉,現(xiàn)在孩子上學(xué)去了九府,送完孩子之后...
    栗小白閱讀 987評(píng)論 0 0
  • 女孩子要勇敢要保護(hù)好自己 文 | 小柒 這個(gè)主題,我想了幾天覆致,遲遲沒有動(dòng)筆侄旬,因?yàn)槲抑溃覍懖缓谩?猶豫了很久煌妈,我...
    小柒吖哩閱讀 680評(píng)論 0 0