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

  1. 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

  2. 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: thiscontrollerUsed to displayUIYou can replace it with yourselfcontroller.
  • GesturesView: used todotInitialization and layout of buttons,
  • PointView:dotGesture 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:

  1. First get the slide point, iterate over all of themPointViewTo determine whether the point is at aGestures buttonRange, record the status in the range, otherwise no processing;
  2. judgeself.startPointWhether it isCGPointZeroIf forYESWill be theGestures buttonthecenterAssigned toself.startPoint;
  3. Judge the gesture buttoncenterWhether included inself.selectedViewCenter, if isYES, ignoring the record, isNORecords the center point forLine drawing, also record theGestures buttontheID, used to record and save gesture password;
  4. ifself.startPointDon’t forCGPointZero, then the current sliding point is recordedself.endPointAnd 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:

  1. First removeself.lineLayer.self.linePath.Otherwise you’ll find that as you slide, you’ll get a lot of lines.;
  2. Set up theself.linePathAnd iterate overself.selectedViewCenterforself.linePathAdd the node, and finallyself.endPointTo finish the slide,self.endPointIs the point of the current sliding position);
  3. Set up theself.lineLayerAnd 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:
  1. willself.endPointSet toself.selectedViewCenter.lastObjectIf theself.endPointOr forCGPointZero, it indicates that the slide is notGestures buttonRange, do no processing, otherwise continue the following logic processing;
  2. Call again-(void)p_drawLinesLine drawing;
  3. Judgment isSet gesture passwordorGestures to unlock;
    1. Set gesture password:
      1. If selectedGestures buttonThe number is less than4To set upself.touchEnd = NOSo that it can be reset,returnEnd the setting;
      2. If set toGestures buttonIf requirements are met, callself.gestureBlock(self.selectedView)Pass the gesture password back toThe controller;
    2. Gestures to unlock:
      1. To get local storageGestures password;I’m using theta hereNSUserDefaultsIn fact, this is not safe, recommended useKeychainAnd I will use it in future updatesKeychainYou have used keychain to save the password. For details, seeDemo
      2. ifself.selectedViewIf the password is the same as the local gesture password, it is unlocked successfully and setpointView.isSuccess = YESchangeGestures buttonStyle, etc., and callself.unlockBlock(YES)To informThe controllerResults;
      3. Otherwise, the unlock fails.pointView.isError = YESchangeGestures buttonStyle, etc., and callself.unlockBlock(NO)To informThe controllerResults;
- (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.