最近有需求要求换成和支付宝一样样式的手势密码,带箭头指示的。
如图:
圆圈画图类:
//
// 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..
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
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
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
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