手势密码demo--swift4.0

最近有需求要求换成和支付宝一样样式的手势密码,带箭头指示的。

如图:

 

圆圈画图类:

 

 

//
//  XCTheCircleView.swift
//  手势密码-有箭头指示
//
//  Created by 小崔 on 2018/11/8.
//  Copyright © 2018年 小崔. All rights reserved.
//

import UIKit
/** 圆的状态 */
enum XCTheCircleViewState:Int {
   case XCTheCircleViewStateNormal               = 1
   case XCTheCircleViewStateSelected           = 2
   case XCTheCircleViewStateError              = 3
   case XCTheCircleViewStateLastOneSelected    = 4
   case XCTheCircleViewStateLastOneError       = 5
}
class XCTheCircleView: UIView {
    /** 所处状态 */
    var _state:XCTheCircleViewState?
    
    var state:XCTheCircleViewState?
    {
        get{
            return _state
        }
        set{
            _state = newValue
            self.setNeedsDisplay()
        }
    }
    /** 类型 */
    var type:XCTheCircleViewType?
    /** 是否有箭头 默认YES */
    var arrow:Bool = isArrow
    /** 角度 */
    var _angle:CGFloat = 0
    
    var angle:CGFloat?
    {
        get{
            return _angle
        }
        set{
            _angle = (newValue)!
            self.setNeedsDisplay()
        }
    }
    /** 初始化 */
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = circleOutNormalBackColor
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func draw(_ rect: CGRect) {
        let  ctx = UIGraphicsGetCurrentContext()
        // 上下文旋转
        self.tansformCtx(ctx: ctx!, rect: rect)
        // 画外空心圆
        self.drawOutCircleWithCtx(ctx: ctx!, rect:rect)
        // 画内实心圆
        self.drawInCircleWithCtx(ctx:ctx!,rect:rect)
        // 画三角形
        self.drawTrangleWithCtx(ctx: ctx! , rect:rect)   
    }
    
}
//MARK:- 绘制界面的方法
extension XCTheCircleView{
    //MARK:- 上下文旋转
    func tansformCtx(ctx:CGContext,rect:CGRect){
        let translateXY = rect.size.width * 0.5
        ctx.translateBy(x: translateXY, y: translateXY)
        ctx.rotate(by: self._angle)
        ctx.translateBy(x: -translateXY, y: -translateXY)
    }
    //MARK:- 画外空心圆
    func drawOutCircleWithCtx(ctx:CGContext,rect:CGRect){
        let borderW = circleOutBorderWidth
        let circleRect:CGRect = CGRect(x: borderW, y: borderW, width: rect.size.width-borderW*2, height:  rect.size.height-borderW*2)
        let circlePath:CGMutablePath = CGMutablePath()
        circlePath.addEllipse(in: circleRect)
        ctx.addPath(circlePath)
        self.outCircleColor().set()
        ctx.setLineWidth(borderW)
        ctx.strokePath()
    }
    
    //MARK:- 画内实心圆
    func drawInCircleWithCtx(ctx:CGContext,rect:CGRect){
        let  radio:CGFloat = circleInRadio
        let  circleX:CGFloat = rect.size.width/2 * (1-radio) + circleOutBorderWidth;
        let  circleY:CGFloat = rect.size.height/2 * (1-radio) + circleOutBorderWidth;
        let  circleW:CGFloat = rect.size.width*radio - circleOutBorderWidth*2;
        let  circleH:CGFloat = rect.size.height*radio - circleOutBorderWidth*2;
        let  circlePath:CGMutablePath = CGMutablePath();
        circlePath.addEllipse(in: CGRect(x:circleX, y:circleY, width:circleW, height:circleH))
        self.inCircleColor().set()
        ctx.addPath(circlePath)
        ctx.fillPath()
    }
    
    //MARK:- 画三角形
    func drawTrangleWithCtx(ctx:CGContext,rect:CGRect){
        if (self.arrow) {
            let topPoint:CGPoint = CGPoint(x: rect.size.width/2, y: 10)
            let trianglePath:CGMutablePath = CGMutablePath();
            trianglePath.move(to: topPoint)
            trianglePath.addLine(to: CGPoint(x: topPoint.x - trangleLength/2, y: topPoint.y + trangleLength/2))
             trianglePath.addLine(to: CGPoint(x: topPoint.x + trangleLength/2, y: topPoint.y + trangleLength/2))
            ctx.addPath(trianglePath)
            self.trangleColor().set()
            ctx.fillPath()
        }
        
    }
    //MARK:- 外圆颜色
    func outCircleColor()->UIColor{
        var color:UIColor?
        switch _state {
        case .XCTheCircleViewStateNormal?:
            color = circleOutNormalBorderColor;
            break;
        case .XCTheCircleViewStateSelected?:
            color = circleOutSelectedBorderColor;
            break;
        case .XCTheCircleViewStateError?:
            color = circleOutErrorBorderColor;
            break;
        case .XCTheCircleViewStateLastOneSelected?:
            color = circleOutSelectedBorderColor;
            break;
        case .XCTheCircleViewStateLastOneError?:
            color = circleOutErrorBorderColor;
            break;
        default:
            color = circleOutNormalBorderColor;
            break;
        }
        return color!
    }
  
    
    //MARK:- 内圆颜色
    func inCircleColor()->UIColor{
        var color:UIColor?
        switch _state {
        case .XCTheCircleViewStateNormal?:
            color = circleInNormalColor;
            break;
        case .XCTheCircleViewStateSelected?:
            color = circleInSelectedColor;
            break;
        case .XCTheCircleViewStateError?:
            color = circleInErrorColor;
            break;
        case .XCTheCircleViewStateLastOneSelected?:
            color = circleInSelectedColor;
            break;
        case .XCTheCircleViewStateLastOneError?:
            color = circleInErrorColor;
            break;
        default:
            color = circleInNormalColor;
            break;
        }
        return color!
    }
    
    //MARK:- 三角形颜色
    func trangleColor()->UIColor{
        var color:UIColor?
        switch _state {
        case .XCTheCircleViewStateNormal?:
            color = trangleNormalColor;
            break;
        case .XCTheCircleViewStateSelected?:
            color = trangleSelectedColor;
            break;
        case .XCTheCircleViewStateError?:
            color = trangleErrorColor;
            break;
        case .XCTheCircleViewStateLastOneSelected?:
            color = trangleNormalColor;
            break;
        case .XCTheCircleViewStateLastOneError?:
            color = trangleErrorColor;
            break;
        default:
            color = trangleNormalColor;
            break;
        }
        return color!
    }
}

 

 

画九宫格的类:

//
//  XCGesturesLockView.swift
//  手势密码-有箭头指示
//
//  Created by 小崔 on 2018/11/9.
//  Copyright © 2018年 小崔. All rights reserved.
//

import UIKit
enum XCLockViewType:NSInteger {
    case XCLockViewTypeSetting = 1 // 设置手势密码
    case XCLockViewTypeLogin   = 2 // 登录手势密码
    case XCLockViewTypeVerify  = 3 // 验证手势密码
}
enum XCLockViewState:NSInteger {
    case XCLockViewStateLess         = 1   // 连线个数少于最小值(设置)
    case XCLockViewStateFirstFinish  = 2   // 提示再次绘制以确认(设置)
    case XCLockViewStateSecondFinish = 3   // 两次绘制一致可保存(设置)
    case XCLockViewStateSecondError  = 4   // 两次绘制路径不一致(设置)
    case XCLockViewStateLoginFinish  = 5   // 手势密码登录成功(登录)
    case XCLockViewStateLoginError   = 6   // 手势密码登录失败(登录)
    case XCLockViewStateVerifyFinish = 7   // 修改密码验证成功(验证)
    case XCLockViewStateVerifyError  = 8   // 修改密码验证失败(验证)
    
}
typealias GestureBlock = (_ seletedArray:[XCTheCircleView],_ selectedValue:String) -> ()

protocol XCLockViewDelegate {
    func lockView(lockView:XCGesturesLockView,state:XCLockViewState)
}

class XCGesturesLockView: UIView {

    var clip = true//是否剪辑
    var arrow = isArrow//是否有箭头
    var type:XCLockViewType?
    var delegate:XCLockViewDelegate?
    var currentPoint:CGPoint?
    var selectedArray = [XCTheCircleView]()
    let selectedCircleArray=[XCTheCircleView]()
    var iscleaned:Bool?
    var gestureBlock:GestureBlock?
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.lockViewPrepare()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    func initWithType(type:XCLockViewType,clip:Bool,arrow:Bool) {
//    [self lockViewPrepare];
//    self.type  = type;
//    self.clip  = clip;
//    self.arrow = arrow;
    }
    override func draw(_ rect: CGRect) {
       //没有任何选中则return
        if self.selectedArray.count > 0{
            let tempState = self.selectedArray.first?.state
            let linColor = tempState == XCTheCircleViewState.XCTheCircleViewStateError ? lineErrorColor:lineSelectedColor
            let ctx = UIGraphicsGetCurrentContext()
            ctx?.addRect(rect)
            for (_,circle) in self.subviews.enumerated(){
                ctx?.addEllipse(in: circle.frame)
            }
            ctx?.clip()
            for i in 0..<self.selectedArray.count{
                let circle = self.selectedArray[i]
                i==0 ? ctx?.move(to: CGPoint(x:circle.center.x,y:circle.center.y)) : ctx?.addLine(to: CGPoint(x:circle.center.x,y:circle.center.y))
            }
            // 连接最后一个按钮到手指当前触摸点
            if !(self.currentPoint?.equalTo(CGPoint.zero))!{
                for (_,_) in self.subviews.enumerated(){
                    if tempState == XCTheCircleViewState.XCTheCircleViewStateError{
                        //错误状态下不连接当前点
                    }else{
                        ctx?.addLine(to: CGPoint(x:(self.currentPoint?.x)!,y:(self.currentPoint?.y)!))
                    }
                }
            }
            ctx?.setLineCap(CGLineCap.round)
            ctx?.setLineJoin(CGLineJoin.round)
            ctx?.setLineWidth(lineWidth)
            linColor.set()
            ctx?.strokePath()
        }
    }
    func lockViewPrepare(){
        self.backgroundColor = MybackgroundColor
        for i in 0..<9{
            let circle = XCTheCircleView()
            circle.tag = i + 1;
            self.addSubview(circle)
        }
    }
    override func layoutSubviews() {
        super.layoutSubviews()
        let itemViewWH = circleOutRadius * 2
        let itemMargin = (self.bounds.size.width - 3*itemViewWH)/3
        for (index,circle) in self.subviews.enumerated(){
            let row:CGFloat = CGFloat(index % 3)
            let col:CGFloat = CGFloat(index / 3);
            let x = itemMargin * row + row*itemViewWH + itemMargin/2
            let y = itemMargin*col + col*itemViewWH + itemMargin/2
            circle.tag = index + 1
            circle.frame = CGRect(x: x, y: y, width: itemViewWH, height: itemViewWH)
        }
    }
    
    //MARK:- touch began / touch moved / touch end
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.resetGesture()//清空手势
        self.currentPoint = CGPoint.zero
        let touch:UITouch = (((touches as NSSet).anyObject() as AnyObject) as! UITouch)
        let point = touch.location(in: self)
        for (_,circle) in self.subviews.enumerated() {
            if (circle.frame.contains(point)){
                (circle as! XCTheCircleView).state = XCTheCircleViewState.XCTheCircleViewStateSelected
                self.selectedArray.append(circle as! XCTheCircleView)
            }
        }
        self.selectedArray.last?.state = XCTheCircleViewState.XCTheCircleViewStateLastOneSelected
        self.setNeedsDisplay()
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        self.currentPoint = CGPoint.zero
        let touch = (((touches as NSSet).anyObject() as AnyObject) as! UITouch)
        let point = touch.location(in: self)
        for (_,circle) in self.subviews.enumerated() {
            if circle.frame.contains(point){
                if !(self.selectedArray.contains(circle as! XCTheCircleView)) {
                    (circle as! XCTheCircleView).state = XCTheCircleViewState.XCTheCircleViewStateSelected
                    self.selectedArray.append(circle as! XCTheCircleView)
                    //move过程中连线(包含跳跃连线的处理)
                    self.calculateAngleAndConnectJumpedCircle()
                }
            }else{
                self.currentPoint = point
            }
        }
        for (_,circle) in self.selectedArray.enumerated() {
            circle.state = XCTheCircleViewState.XCTheCircleViewStateSelected
        }
        self.selectedArray.last?.state = XCTheCircleViewState.XCTheCircleViewStateLastOneSelected
        self.setNeedsDisplay()
    }
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        if self.selectedArray.count>0 && self.gestureBlock != nil{
            var  gestureStr = ""
            for circle in self.selectedArray{
                gestureStr = gestureStr+"\(circle.tag)"
            }
            self.gestureBlock!(self.selectedArray,gestureStr)
        }
        //重置
        if self.selectedArray.first?.state == XCTheCircleViewState.XCTheCircleViewStateError{
            DispatchQueue.main.asyncAfter(deadline: .now()+1, execute: {
                self.resetGesture()
            })
        }else{
            self.resetGesture()
        }
    }
    
    //MARK:- 手势清空重置操作
    func resetGesture(){
         objc_sync_enter(self)
         self.changeCirclesWithSate(state: XCTheCircleViewState.XCTheCircleViewStateNormal)
         self.selectedArray.removeAll()
         objc_sync_exit(self)
    }
    //MARK:- 改变选中数组子控件状态
    func changeCirclesWithSate(state:XCTheCircleViewState){
        for (_,circle) in (self.selectedArray.enumerated()){
            circle.state = state
            if state == .XCTheCircleViewStateNormal{
                circle.angle = 0//角度清空
            }
        }
        self.setNeedsDisplay()
    }
    
    //MARK:- 每添加一个圆计算一次方向,同时处理跳跃连线
    func calculateAngleAndConnectJumpedCircle(){
        print("self.selectedArray->\(self.selectedArray.count)")
        if (self.selectedArray.count > 1){
            // 最后一个对象
            let last1:XCTheCircleView = self.selectedArray.last!
            // 倒数第二个对象
            let last2:XCTheCircleView = self.selectedArray[self.selectedArray.count-2]
            // 计算角度(反正切)
            last2.angle = CGFloat(atan2(Float(last1.center.y-last2.center.y), Float(last1.center.x-last2.center.x)))+CGFloat(Double.pi/2)
            // 跳跃连线问题
             let jumpedCircle = self.selectedCircleContainPoint(point: self.centerPointWithPoint1(point1: last1.center, point2: last2.center))
            if !self.selectedArray.contains(jumpedCircle) && jumpedCircle.tag != 1000{
            // 把跳跃的圆添加到已选择圆的数组(插入到倒数第二个)
                self.selectedArray.insert(jumpedCircle, at: self.selectedArray.count - 1)
            }
        }
    }
    
    //MARK:-  提供两个点返回他们中点
    func centerPointWithPoint1(point1:CGPoint,point2:CGPoint)->CGPoint{
        let x1 = fmax(point1.x, point2.x)
        let x2 = fmin(point1.x, point2.x)
        let y1 = fmax(point1.y, point2.y)
        let y2 = fmin(point1.y, point2.y)
        return CGPoint(x:(x1+x2)/2, y:(y1+y2)/2)
    }
    //MARK:- 判断点是否被圆包含(包含返回圆否则返回nil)
    func selectedCircleContainPoint(point:CGPoint) -> XCTheCircleView{
        var centerCircle = XCTheCircleView()
        centerCircle.tag = 1000
        for (_,circle) in self.subviews.enumerated() {
            if circle.frame.contains(point){
                centerCircle = (circle as? XCTheCircleView)!
                centerCircle.tag = 10
            }
        }
        if !self.selectedArray.contains(centerCircle){
            // 跳跃的点角度和已选择的倒数第二个角度一致
            centerCircle.angle = self.selectedArray[self.selectedArray.count-2].angle
        }
        return centerCircle
    }
    
}

 

demo下载地址:https://download.csdn.net/download/koocui/10780025

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值