效果
網上找到一個使用圖片的方案,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地址