preface
I have always wanted to write a blog. On the one hand, I can record what I have learned and studied, and on the other hand, I can share what I have written for others to refer to and receive suggestions from others, so as to improve my projects and improve my technology. This is also a good way of technical communication. But didn’t know how to write before? How to sum it up? After some observation and learning, finally decided to try the water 😂. This is my first blog post.
This blog is my fingerprint unlock (system API) and gesture unlock (CAShapeLayer) functions based on iOS system.
Since I learned CAAnimation by myself and the boss of the company said that I could pre-study various unlocking methods (which I had not done before), I wanted to realize the commonly used unlocking methods by myself: fingerprint unlocking and gesture unlocking.
Fingerprint unlock
IOS fingerprint unlocking is actually very simple, because the system already provides apis for you, you just need to make some simple judgments and appropriate calls.
The first step
First import header file # import < > LocalAuthentication/LocalAuthentication. H
Check whether TouchID is enabled. If so, directly verify the fingerprint. If not, TouchID needs to be enabled first
// Check whether TouchID is enabled
[[[NSUserDefaults standardUserDefaults] objectForKey:@"OpenTouchID"] boolValue]
Copy the code
The second step
- If TouchID is not enabled, ask if it is enabled
- (void)p_openTouchID
{
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@" Warm tips" message:"TouchID enabled?" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@"YES" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
/ / open TouchID
[[NSUserDefaults standardUserDefaults] setObject:@(YES) forKey:@"OpenTouchID"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"OpenTouchIDSuccess" object:nil userInfo:nil];
}]];
[alertController addAction:[UIAlertAction actionWithTitle:@"NO" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
// Don't turn on TouchID
[[NSUserDefaults standardUserDefaults] setObject:@(NO) forKey:@"OpenTouchID"];
[[NSNotificationCenter defaultCenter] postNotificationName:@"OpenTouchIDSuccess" object:nil userInfo:nil];
}]];
[self presentViewController:alertController animated:YES completion:nil];
});
}
Copy the code
- Already open TouchID
- (void)p_touchID
{
dispatch_async(dispatch_get_main_queue(), ^{
LAContext *context = [[LAContext alloc] init];
NSError *error = nil;
// Check whether TouchID is supported
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:@"TouchID Text" reply:^(BOOL success, NSError * _Nullable error) {
if (success) {// The fingerprint verification succeeded
[[NSNotificationCenter defaultCenter] postNotificationName:@"UnlockLoginSuccess" object:nil];
}else {// Fingerprint verification failed
switch (error.code)
{
case LAErrorAuthenticationFailed:
{
NSLog(@" Authorization failed"); // -1 Indicates three consecutive fingerprint identification errors
[[NSNotificationCenter defaultCenter] postNotificationName:@"touchIDFailed" object:nil];
}
break;
case LAErrorUserCancel:
{
NSLog(@" User unvalidate Touch ID"); // -2 Click the Cancel button in the TouchID dialog box
[self dismissViewControllerAnimated:YES completion:nil];
}
break;
case LAErrorUserFallback:
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"touchIDFailed" object:nil];
NSLog(@" User chooses to input password, switch main thread processing"); // -3 Click enter password button in the TouchID dialog box
}];
}
break;
case LAErrorSystemCancel:
{
NSLog(@" Cancel authorization, such as other applications, user independent"); // -4 The TouchID dialog box is cancelled by the system, such as pressing the Home or power button
}
break;
case LAErrorPasscodeNotSet:
{
NSLog(@" Device system does not set password"); / / - 5
}
break;
case LAErrorBiometryNotAvailable:
{
NSLog(@" Device is not set with Touch ID"); / / - 6
}
break;
case LAErrorBiometryNotEnrolled: // Authentication could not start, because Touch ID has no enrolled fingers
{
NSLog(@" User did not input fingerprint"); / / - 7
}
break;
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0
case LAErrorBiometryLockout: //Authentication was not successful, because there were too many failed Touch ID attempts and Touch ID is now locked. Passcode is required to unlock Touch ID, E.g. Evaluating LAPolicyDeviceOwnerAuthenticationWithBiometrics will ask for user passcode as a prerequisite to Touch many times continuously The ID authentication fails, and the Touch ID is locked. The user needs to enter the password to unlock the device
{
NSLog(@"Touch ID is locked, user needs to enter password to unlock"); // -8 The TouchID function is locked for five consecutive fingerprint identification errors, and the system password needs to be entered next time
}
break;
case LAErrorAppCancel:
{
NSLog("APP is suspended under circumstances beyond user's control"); / / - 9
}
break;
case LAErrorInvalidContext:
{
NSLog("LAContext expired before it was passed to this call"); / / - 10
}
break;
#else
#endif
default: {[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(In other cases, switch main thread processing);
}];
break; }}}}]; }else {
/ / does not support
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@" Warm tips" message:"This device does not support TouchID" preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:@ "complete" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alertController animated:YES completion:nil]; }}); }Copy the code
NSNotificationCenter note: the code for different operation interface jump, after resetting the window. The rootViewController, negligible.
This is the end of fingerprint unlock, a very simple API call.
Gestures to unlock
In fact, when I was not in touch with iOS development before and at the beginning, I felt it was very difficult to unlock gesture, and I had no idea how to achieve it. However, when I was learning CAAnimation by myself, I suddenly came up with a plan to realize gesture unlocking. Here is how TO do it:
conceived
-
How does gesture unlock verify that you swipe correctly?
Actually gestures to unlock and validation of the input password is the same, when you draw the UI, you can give a * * dot a id, when you set the gestures, dots will slide to the corresponding * * id in an ordered set, and save up, and then verify the login, use an ordered set another record your current sliding to * dot * id, Then compare it with the one saved locally to achieve the purpose of verification
-
How to implement the UI concretely?
I had thought of several ways to implement it before, but all of them were passed away. Until I learned CAAnimation by myself, I suddenly realized that there was a good way to implement it: ----CAShapeLayer
In fact, when you have the answers to these two questions, you will have done most of your gesture unlocking. The rest is just typing code.
implementation(See link at the end of the article for project code)
First a few renderers :(due to my limited artistic cells, so in order to look good, the UI of the interface is unlocked by referring to the gesture of QQ security center)
The directory structure
GesturesViewController
: thiscontroller
Used to displayUI
You can replace it with yourselfcontroller
.GesturesView
: used todot
Initialization and layout of buttons,PointView
:dot
Gesture buttons.
GesturesView and PointView are the two classes in which most of the logic is stored:
PointView(UI) PointView(UI)
PointView.h
- (instancetype)initWithFrame:(CGRect)frame
withID:(NSString *)ID;
@property (nonatomic.copy.readonly) NSString *ID;
/ / selected
@property (nonatomic.assign) BOOL isSelected;
// Unlock failed
@property (nonatomic.assign) BOOL isError;
// The account is unlocked successfully
@property (nonatomic.assign) BOOL isSuccess;
Copy the code
-
-initWithFrame:withID: Passes the frameheID to initialize the PointView,
-
ID: read-only, used to obtain the ID externally,
-
An isSelected isError, isSuccess: used to judge the PointView of state to show no UI.
PointView.m
Initialize three CAShapeLayer by lazy loading
#pragma mark - Lazy loading
// Outer gesture button
- (CAShapeLayer *)contentLayer
{
if(! _contentLayer) { _contentLayer = [CAShapeLayer layer];
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(2.0.2.0, SELF_WIDTH - 4.0, SELF_HEIGHT - 4.0) cornerRadius:(SELF_WIDTH - 4.0) / 2.0];
_contentLayer.path = path.CGPath;
_contentLayer.fillColor = RGBCOLOR(46.0.47.0.50.0).CGColor;
_contentLayer.strokeColor = RGBCOLOR(26.0.27.0.29.0).CGColor;
_contentLayer.strokeStart = 0;
_contentLayer.strokeEnd = 1;
_contentLayer.lineWidth = 2;
_contentLayer.cornerRadius = self.bounds.size.width / 2.0;
}
return _contentLayer;
}
// Gesture button border
- (CAShapeLayer *)borderLayer
{
if(! _borderLayer) { _borderLayer = [CAShapeLayer layer];
UIBezierPath *borderPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(SELF_WIDTH / 2.0, SELF_HEIGHT / 2.0) radius:SELF_WIDTH / 2.0 startAngle:0 endAngle:2 * M_PI clockwise:NO];
_borderLayer.strokeColor = RGBCOLOR(105.0.108.0.111.0).CGColor;
_borderLayer.fillColor = [UIColor clearColor].CGColor;
_borderLayer.strokeEnd = 1;
_borderLayer.strokeStart = 0;
_borderLayer.lineWidth = 2;
_borderLayer.path = borderPath.CGPath;
}
return _borderLayer;
}
// Middle style when selected
- (CAShapeLayer *)centerLayer
{
if(! _centerLayer) { _centerLayer = [CAShapeLayer layer];
UIBezierPath *centerPath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(SELF_WIDTH / 2.0 - (SELF_WIDTH - 4.0) / 4.0, SELF_HEIGHT / 2.0 - (SELF_HEIGHT - 4.0) / 4.0, (SELF_WIDTH - 4.0) / 2.0, (SELF_WIDTH - 4.0) / 2.0) cornerRadius:(SELF_WIDTH - 4.0) / 4.0];
_centerLayer.path = centerPath.CGPath;
_centerLayer.lineWidth = 3;
_centerLayer.strokeColor = [UIColor colorWithWhite:0 alpha:0.7].CGColor;
_centerLayer.fillColor = RGBCOLOR(30.0.180.0.244.0).CGColor;
}
return _centerLayer;
}
Copy the code
Sets the UI state of the PointView
// Display three states according to the situation
- (void)setIsSuccess:(BOOL)isSuccess
{
_isSuccess = isSuccess;
if (_isSuccess) {
self.centerLayer.fillColor = RGBCOLOR(43.0.210.0.110.0).CGColor;
}else {
self.centerLayer.fillColor = RGBCOLOR(30.0.180.0.244.0).CGColor; }} - (void)setIsSelected:(BOOL)isSelected
{
_isSelected = isSelected;
if (_isSelected) {
self.centerLayer.hidden = NO;
self.borderLayer.strokeColor = RGBCOLOR(30.0.180.0.244.0).CGColor;
}else {
self.centerLayer.hidden = YES;
self.borderLayer.strokeColor = RGBCOLOR(105.0.108.0.111.0).CGColor; }} - (void)setIsError:(BOOL)isError
{
_isError = isError;
if (_isError) {
self.centerLayer.fillColor = RGBCOLOR(222.0.64.0.60.0).CGColor;
}else {
self.centerLayer.fillColor = RGBCOLOR(30.0.180.0.244.0).CGColor; }}Copy the code
GesturesView (basically all the logic is in this)
GesturesView.h
// Return the selected ID
typedef void (^GestureBlock)(NSArray *selectedID);
// Return the result of the gesture validation
typedef void (^UnlockBlock)(BOOL isSuccess);
// Failed to set gesture
typedef void (^SettingBlock)(void);
@interface GesturesView : UIView
/** Return the gesture password */ when setting the password
@property (nonatomic.copy) GestureBlock gestureBlock;
/** Returns whether the unlock succeeded or failed */
@property (nonatomic.copy) UnlockBlock unlockBlock;
/** The password is set successfully (the password should not be less than four dots) */
@property (nonatomic.copy) SettingBlock settingBlock;
/** Determine whether to set gesture or gesture unlock */
@property (nonatomic.assign) BOOL settingGesture;
Copy the code
Here I declare three blocks:
GestureBlock:
Pass the selected ordered set of ids back to the controller,UnlockBlock:
Return the gesture verification result,SettingBlcok:
Failed to set gesture
Properties:
gestureBlock,unlockBlock,settingBlock:
Are instances of the corresponding blocks,settingGesture:
Used to determine whether to set gesture or gesture unlock
Gesturesview.h (the main logical implementation part)
Private properties section:
// Mutable array, used to store the initialized click button
@property (nonatomic.strong) NSMutableArray *pointViews;
// Record the starting point of the gesture slide
@property (nonatomic.assign) CGPoint startPoint;
// Record the end point of the gesture slide
@property (nonatomic.assign) CGPoint endPoint;
// Store the selected button ID
@property (nonatomic.strong) NSMutableArray *selectedView;
// The gesture slides over the line of points
@property (nonatomic.strong) CAShapeLayer *lineLayer;
// Gesture slide path
@property (nonatomic.strong) UIBezierPath *linePath;
// Used to store selected buttons
@property (nonatomic.strong) NSMutableArray *selectedViewCenter;
// Check whether the slide is finished
@property (nonatomic.assign) BOOL touchEnd;
Copy the code
Code implementation part:
Initialize startPoint, endPoint, and 9 PointView buttons, default startPoint and endPoint to 0, and set the PointView ID:
// Initialize the start and end points
self.startPoint = CGPointZero;
self.endPoint = CGPointZero;
// Layout gesture buttons (with custom omnipotent initialization method)
for (int i = 0; i<9 ; i++) {
PointView *pointView = [[PointView alloc] initWithFrame:CGRectMake((i % 3) * (SELF_WIDTH / 2.0 - 31.0) + 1, (i / 3) * (SELF_HEIGHT / 2.0 - 31.0) + 1.60.60)
withID:[NSString stringWithFormat:@"gestures %d",i + 1]];
[self addSubview:pointView];
[self.pointViews addObject:pointView];
}
Copy the code
Sliding events:
- Start sliding:
If self.touchEnd is YES then return with NO to begin the following processing:
- First get the slide point, iterate over all of them
PointView
To determine whether the point is at aGestures button
Range, record the status in the range, otherwise no processing; - judge
self.startPoint
Whether it isCGPointZero
If forYES
Will be theGestures button
thecenter
Assigned toself.startPoint
; - Judge the gesture button
center
Whether included inself.selectedViewCenter
, if isYES
, ignoring the record, isNO
Records the center point forLine drawing
, also record theGestures button
theID
, used to record and save gesture password; - if
self.startPoint
Don’t forCGPointZero
, then the current sliding point is recordedself.endPoint
And underline.
/ / touch events
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
if (self.touchEnd) {
return;
}
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
// Determine whether the gesture slide is within the gesture button range
for (PointView *pointView in self.pointViews) {
// Slide to the gesture button range to record the status
if (CGRectContainsPoint(pointView.frame, point)) {
// If the start button is zero, record the start button, otherwise no need to record the start button
if (CGPointEqualToPoint(self.startPoint, CGPointZero)) {
self.startPoint = pointView.center;
}
// Determine whether the center point of the gesture button is recorded, if not, it is recorded
if(! [self.selectedViewCenter containsObject:[NSValue valueWithCGPoint:pointView.center]]) {
[self.selectedViewCenter addObject:[NSValue valueWithCGPoint:pointView.center]];
}
// Check whether the gesture button is already selected
if(! [self.selectedView containsObject:pointView.ID]) {
[self.selectedView addObject:pointView.ID];
pointView.isSelected = YES; }}}// If the start point is not zero, record the end point, otherwise skip no record
if (!CGPointEqualToPoint(self.startPoint, CGPointZero)) {
self.endPoint = point;
[selfp_drawLines]; }}Copy the code
- Draw lines:
If self.touchEnd is YES then return and start drawing a line for NO:
- First remove
self.lineLayer
.self.linePath
.Otherwise you’ll find that as you slide, you’ll get a lot of lines.; - Set up the
self.linePath
And iterate overself.selectedViewCenter
forself.linePath
Add the node, and finallyself.endPoint
To finish the slide,self.endPoint
Is the point of the current sliding position); - Set up the
self.lineLayer
And add the corresponding properties toself.layer
.
Draw a line / /
- (void)p_drawLines
{
// End the gesture slide without drawing a line
if (self.touchEnd) {
return;
}
// Remove the path point and lineLayer
[self.lineLayer removeFromSuperlayer];
[self.linePath removeAllPoints];
Draw a line / /
[self.linePath moveToPoint:self.startPoint];
for (NSValue *pointValue in self.selectedViewCenter) {
[self.linePath addLineToPoint:[pointValue CGPointValue]];
}
[self.linePath addLineToPoint:self.endPoint];
self.lineLayer.path = self.linePath.CGPath;
self.lineLayer.lineWidth = 4.0;
self.lineLayer.strokeColor = RGBCOLOR(30.0.180.0.244.0).CGColor;
self.lineLayer.fillColor = [UIColor clearColor].CGColor;
[self.layer addSublayer:self.lineLayer];
self.layer.masksToBounds = YES;
}
Copy the code
- End slide:
- will
self.endPoint
Set toself.selectedViewCenter.lastObject
If theself.endPoint
Or forCGPointZero
, it indicates that the slide is notGestures button
Range, do no processing, otherwise continue the following logic processing; - Call again
-(void)p_drawLines
Line drawing; - Judgment is
Set gesture password
orGestures to unlock
;Set gesture password
:- If selected
Gestures button
The number is less than4To set upself.touchEnd = NO
So that it can be reset,return
End the setting; - If set to
Gestures button
If requirements are met, callself.gestureBlock(self.selectedView)
Pass the gesture password back toThe controller
;
- If selected
Gestures to unlock
:- To get local storage
Gestures password
;I’m using theta hereYou have used keychain to save the password. For details, seeDemoNSUserDefaults
In fact, this is not safe, recommended useKeychain
And I will use it in future updatesKeychain
- if
self.selectedView
If the password is the same as the local gesture password, it is unlocked successfully and setpointView.isSuccess = YES
changeGestures button
Style, etc., and callself.unlockBlock(YES)
To informThe controller
Results; - Otherwise, the unlock fails.
pointView.isError = YES
changeGestures button
Style, etc., and callself.unlockBlock(NO)
To informThe controller
Results;
- To get local storage
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// When the end gesture slides, set the end button to the center of the last gesture button and draw a line
self.endPoint = [self.selectedViewCenter.lastObject CGPointValue];
// If the endPoint is still zero, no operation is performed
if (CGPointEqualToPoint(self.endPoint, CGPointZero)) {
return;
}
[self p_drawLines];
// Change the state of the end of the swipe gesture. If it is yes, the swipe gesture cannot be underlined
self.touchEnd = YES;
// When setting the gesture, return the password at the time of setting, otherwise proceed with the following operations to unlock the gesture
if (_gestureBlock && _settingGesture) {
// Gesture password should not be less than 4 dots
if (self.selectedView.count < 4) {
self.touchEnd = NO;
for (PointView *pointView in self.pointViews) {
pointView.isSelected = NO;
}
[self.lineLayer removeFromSuperlayer];
[self.selectedView removeAllObjects];
self.startPoint = CGPointZero;
self.endPoint = CGPointZero;
[self.selectedViewCenter removeAllObjects];
if (_settingBlock) {
self.settingBlock();
}
return;
}
_gestureBlock(self.selectedView);
return;
}
// Gesture to unlock
NSArray *selectedID = [[NSUserDefaults standardUserDefaults] objectForKey:@"GestureUnlock"];
// The account is unlocked successfully
if ([self.selectedView isEqualToArray:selectedID]) {
// Unlock successfully, traverse the pointView and set the status to success
for (PointView *pointView in self.pointViews) {
pointView.isSuccess = YES;
}
self.lineLayer.strokeColor = RGBCOLOR(43.0.210.0.110.0).CGColor;
if (_unlockBlock) {
self.unlockBlock(YES);
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"UnlockLoginSuccess" object:nil];
});
}else {// Unlock failed
// Unlock failed, traverse pointView and set to failed state
for (PointView *pointView in self.pointViews) {
pointView.isError = YES;
}
self.lineLayer.strokeColor = RGBCOLOR(222.0.64.0.60.0).CGColor;
if (_unlockBlock) {
self.unlockBlock(NO); }}}Copy the code
This is where all the logic of gesture unlocking is realized. I was worried about any problems before implementation, but after implementation, it feels very simple.
The last
Hopefully this article will help some people. For the code and some of the blog norms hope we understand, behind will also slowly to optimize. Finally, the Demo link is attached to demo-Github.