效果
网上找到一个使用图片的方案,KKGestureLockView,但是需求的话如果要做动画美观,你必须自己进行绘制,在这个基础上进行自定义,先看看效果
手势解锁
1.首先手势解锁区域是一个个自定义的button,当接收到用户手势的时候,根据坐标把对应的button放进数组,进一步后续判断
2.检测到用户手势滑动的时候让按钮不断进行重绘,形成动画
3.然后手势划过的线也是一个盖在解锁区域上面的一个View,根据左边进行路径绘制
1.初始化
- (void)_lockViewInitialize{
self.backgroundColor = [UIColor clearColor];
self.lineColor = [[UIColor blackColor] colorWithAlphaComponent:0.3];
self.lineWidth = kLineDefaultWidth;
self.isShowInner = YES;
// 解锁区域
self.contentInsets = UIEdgeInsetsMake(0, 0, 0, 0);
self.contentView = [[UIView alloc] initWithFrame:UIEdgeInsetsInsetRect(self.bounds, self.contentInsets)];
// self.contentView.backgroundColor = [UIColor yellowColor];
[self addSubview:self.contentView];
// 手势轨迹区域
self.gestureLineView = [[KKGestureLineView alloc] initWithFrame:self.bounds];
self.gestureLineView.backgroundColor = [UIColor clearColor];
[self addSubview:self.gestureLineView];
self.buttonSize = CGSizeMake(kNodeDefaultWidth, kNodeDefaultHeight);
// 调用数量的setter方法进行按钮的添加
self.numberOfGestureNodes = kNumberOfNodes;
self.gestureNodesPerRow = kNodesPerRow;
}
2.定坐标
- (void)layoutSubviews{
[super layoutSubviews];
_gestureLineView.lineColor = [UIColor redColor];
_gestureLineView.lineWidth = self.isDotShow ? 0 : kLineDefaultWidth;
self.contentView.frame = UIEdgeInsetsInsetRect(self.bounds, self.contentInsets);
CGFloat horizontalNodeMargin = (self.contentView.bounds.size.width - self.buttonSize.width * self.gestureNodesPerRow)/(self.gestureNodesPerRow - 1);
NSUInteger numberOfRows = ceilf((self.numberOfGestureNodes * 1.0 / self.gestureNodesPerRow));
CGFloat verticalNodeMargin = (self.contentView.bounds.size.height - self.buttonSize.height *numberOfRows)/(numberOfRows - 1);
for (int i = 0; i < self.numberOfGestureNodes ; i++) {
int row = i / self.gestureNodesPerRow;
int column = i % self.gestureNodesPerRow;
KKGestureLockItemView *button = [self.buttons objectAtIndex:i];
button.nodeWith = _nodeWidth?_nodeWidth:18;
button.isShowInner = _isShowInner;
button.frame = CGRectMake(floorf((self.buttonSize.width + horizontalNodeMargin) * column), floorf((self.buttonSize.height + verticalNodeMargin) * row), self.buttonSize.width, self.buttonSize.height);
}
}
3.核心解锁区域三个方法
*touchesBegan
*touchesMoved
*touchesEnded
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// 获取开始的坐标
UITouch *touch = [touches anyObject];
CGPoint locationInContentView = [touch locationInView:self.contentView];
// 根据坐标获取到对应的按钮
KKGestureLockItemView *touchedButton = [self _buttonContainsThePoint:locationInContentView];
// 如果开始的时候不是按钮区域不进行绘制
if (touchedButton != nil) {
// 触发到按钮进行动画
[touchedButton setItemViewType:KKGestureLockItemTypeSelect];
[touchedButton startAnimation];//开始动画
// 添加到选择的数组
[_gestureLineView.selectedButtons addObject:touchedButton];
_gestureLineView.trackedLocationInContentView = locationInContentView;
if (_delegateFlags.didBeginWithPasscode) {
[self.delegate gestureLockView:self didBeginWithPasscode:[NSString stringWithFormat:@"%d",(int)touchedButton.tag]];
}
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
// 获取到坐标
UITouch *touch = [touches anyObject];
CGPoint locationInContentView = [touch locationInView:self.contentView];
// 手势区域在规定坐标里面
if (CGRectContainsPoint(self.contentView.bounds, locationInContentView)) {
// 如果触发到了按钮区域,按钮进行动画
KKGestureLockItemView *touchedButton = [self _buttonContainsThePoint:locationInContentView];
if (touchedButton != nil && [_gestureLineView.selectedButtons indexOfObject:touchedButton]==NSNotFound) {
[touchedButton setItemViewType:KKGestureLockItemTypeSelect];
[touchedButton startAnimation];//开始动画
[_gestureLineView.selectedButtons addObject:touchedButton];
if ([_gestureLineView.selectedButtons count] == 1) {
//If the touched button is the first button in the selected buttons,
//It's the beginning of the passcode creation
if (_delegateFlags.didBeginWithPasscode) {
[self.delegate gestureLockView:self didBeginWithPasscode:[NSString stringWithFormat:@"%d",(int)touchedButton.tag]];
}
}
}
// 不断绘制轨迹线
_gestureLineView.trackedLocationInContentView = locationInContentView;
[_gestureLineView setNeedsDisplay];
}
}
4.手势的动画无非就是不断调用drawRect
这里介绍几个常用的方法,具体实现都是慢慢试出来的,知道方法就好了,需要的自己下载Demo看吧
* 1. CGContextSetStrokeColorWithColor 边缘线的颜色 CGContextSetFillColorWithColor 填充颜色
* 2. CGContextSetLineWidth 边缘线的宽度
* 3. CGContextAddArc 画一个圆 x,y为圆点坐标,radius半径,startAngle为开始的弧度,endAngle为 结束的弧度,clockwise 0为顺时针,1为逆时针。
* 4. CGContextDrawPath 绘制路径,第一个参数是上下文,第二个参数是kCGPathFill--> 填充 kCGPathStroke-->路劲 kCGPathFillStroke--> 填充+路径
* 5. CGContextStrokePath 类似上面的边框路径绘制
* 6. CGContextFillPath 类似上面的填充绘制
* 7. CGContextClearRect 清理上下文
* 8. CGContextSaveGState 和 CGContextRestoreGState CGContextSaveGState函数的作用是将当前图形状态推入堆栈。之后,您对图形状态所做的修改会影响随后的描画操作,但不影响存储在堆栈中的拷贝。在修改完成后,您可以通过CGContextRestoreGState函数把堆栈顶部的状态弹出,返回到之前的图形状态。这种推入和弹出的方式是回到之前图形状态的快速方法,避免逐个撤消所有的状态修改;这也是将某些状态(比如裁剪路径)恢复到原有设置的唯一方式
* 9. CGContextClip 裁剪上下文
* 10. CGContextAddEllipseInRect 画椭圆
* 11. CGContextAddQuadCurveToPoint 两点之间正余弦波动,双点控制
* 12. CGContextDrawImage 这个会使图片上下点到
小知识,以这个为例 自己用绘图方法画一个带渐变的按钮
- (void)drawRect:(CGRect)rect {
// Drawing code
General Declarations
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = UIGraphicsGetCurrentContext();
Gradient Declarations
CGFloat gradientLocations[] = {0, 0.32, 1};
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)@[(id)UIColor.greenColor.CGColor, (id)[MKJView mixColor1:[UIColor greenColor] color2:[UIColor whiteColor] ratio:0.5].CGColor, (id)UIColor.whiteColor.CGColor], gradientLocations);
Oval Drawing
UIBezierPath* ovalPath = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(0, 0, 24, 24)];
CGContextSaveGState(context);
[ovalPath addClip];
CGContextDrawLinearGradient(context, gradient, CGPointMake(12, 0), CGPointMake(12, 24), 0);
CGContextRestoreGState(context);
Text Drawing
CGRect textRect = CGRectMake(6, 6, 13, 12);
{
NSString* textContent = @"D";
NSMutableParagraphStyle* textStyle = NSMutableParagraphStyle.defaultParagraphStyle.mutableCopy;
textStyle.alignment = NSTextAlignmentLeft;
NSDictionary* textFontAttributes = @{NSFontAttributeName: [UIFont systemFontOfSize: UIFont.labelFontSize], NSForegroundColorAttributeName: UIColor.blackColor, NSParagraphStyleAttributeName: textStyle};
CGFloat textTextHeight = [textContent boundingRectWithSize: CGSizeMake(textRect.size.width, INFINITY) options: NSStringDrawingUsesLineFragmentOrigin attributes: textFontAttributes context: nil].size.height;
CGContextSaveGState(context);
CGContextClipToRect(context, textRect);
[textContent drawInRect: CGRectMake(CGRectGetMinX(textRect), CGRectGetMinY(textRect) + (CGRectGetHeight(textRect) - textTextHeight) / 2, CGRectGetWidth(textRect), textTextHeight) withAttributes: textFontAttributes];
CGContextRestoreGState(context);
}
Cleanup
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
}
+(UIColor *)mixColor1:(UIColor*)color1 color2:(UIColor *)color2 ratio:(CGFloat)ratio
{
if(ratio > 1)
ratio = 1;
const CGFloat * components1 = CGColorGetComponents(color1.CGColor);
const CGFloat * components2 = CGColorGetComponents(color2.CGColor);
// NSLog(@"Red1: %f", components1[0]);
// NSLog(@"Green1: %f", components1[1]);
// NSLog(@"Blue1: %f", components1[2]);
// NSLog(@"Red2: %f", components2[0]);
// NSLog(@"Green2: %f", components2[1]);
// NSLog(@"Blue2: %f", components2[2]);
NSLog(@"ratio = %f",ratio);
CGFloat r = components1[0]*ratio + components2[0]*(1-ratio);
CGFloat g = components1[1]*ratio + components2[1]*(1-ratio);
CGFloat b = components1[2]*ratio + components2[2]*(1-ratio);
// CGFloat alpha = components1[3]*ratio + components2[3]*(1-ratio);
return [UIColor colorWithRed:r green:g blue:b alpha:1];
}
5.指纹解锁
1.canEvaluatePolicy 判断是否支持指纹或者是否开启指纹
2.evaluatePolicy 进行指纹识别校验,弹出系统框
3.导入LocalAuthentication框架
4.指纹识别错误的明细参考参考error明细
- (BOOL)isTouchIDEnableOrNotBySystem
{
#ifdef __IPHONE_8_0
LAContext *myContext = [[LAContext alloc] init];
NSError *authError = nil;
if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError])
{
/**
可以验证指纹 手机支持而且手机开启指纹模式
*/
return YES;
}
else
{
/**
无法验证指纹 手机不支持或者用户未开启指纹模式
*/
return NO;
}
#else
/**
无法验证指纹
*/
return NO;
#endif /* __IPHONE_8_0 */
}
- (void)startVerifyTouchID:(void (^)(void))completeBlock failure:(void (^)(void))failureBlock
{
NSString *touchIDReason = [NSString stringWithFormat:@"我要解锁%@",_appName];
#ifdef __IPHONE_8_0
LAContext *myContext = [[LAContext alloc] init];
NSError *authError = nil;
// Hide "Enter Password" button
myContext.localizedFallbackTitle = @"";
// show the authentication UI
if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError])
{
[myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:touchIDReason
reply:^(BOOL success, NSError *error) {
if (success) {
// User authenticated successfully, take appropriate action
dispatch_async(dispatch_get_main_queue(), ^{
/**
指纹校验 成功
*/
[self verifySucceed:completeBlock];
});
} else {
// User did not authenticate successfully, look at error and take appropriate action
dispatch_async(dispatch_get_main_queue(), ^{
/**
指纹校验 失败
*/
[self authenticatedFailedWithError:error failure:failureBlock];
});
}
}];
}
else
{
// Could not evaluate policy; look at authError and present an appropriate message to user
dispatch_async(dispatch_get_main_queue(), ^{
[self evaluatePolicyFailedWithError:nil];
});
}
#endif /* __IPHONE_8_0 */
}
1.注意这里有一个参数,我们现在用的是LAPolicyDeviceOwnerAuthenticationWithBiometrics,默认就是TouchID失败之后不会跳转到密码设置,如果你换成LAPolicyDeviceOwnerAuthentication,那么就会进行密码设置的跳转,还是根据需求进行配置
2.另一个参数localizedReason就是配置弹窗下显示指纹解锁的提示语
Demo地址