iOS开发基础101-生物识别与手势识别:从 FaceID/TouchID

iOS 生物识别与手势识别:从 FaceID/TouchID 到 UIGestureRecognizer

FaceID/TouchID 为 App 提供了安全便捷的身份验证,手势识别则丰富了用户交互。本文从底层原理讲起,深入解析 LocalAuthentication 框架的安全机制、错误码处理、两种验证策略,以及 7 种手势识别器的用法和冲突处理,给出完整的 OC/Swift 封装代码和常见坑排查。


一、生物识别(FaceID / TouchID)

一句话原理

生物识别是用用户的指纹或面部特征代替密码进行身份验证,验证过程在设备本地的 Secure Enclave(安全隔区)中完成,App 拿不到指纹/面部数据,只能收到"成功"或"失败"的结果。

安全机制:Secure Enclave

  • 指纹和面部数据加密存储在设备的 Secure Enclave 中,不会上传到苹果服务器,也不会被 App 获取。
  • App 只能通过 LocalAuthentication 框架发起验证请求,由系统处理验证过程。
  • 验证结果只有成功/失败,App 无法获取生物特征数据。
  • 这是生物识别安全的核心保证。

设备支持情况

设备 生物识别类型
iPhone X 及以后(无 Home 键) FaceID
iPhone 8 及之前(有 Home 键) TouchID
iPad Pro 11/12.9(第三代+) FaceID
iPad Air/mini(有 Home 键) TouchID
iPhone 15 Pro 及更新(部分) 可能支持 OpticID( under-display TouchID)

1. Info.plist 配置

FaceID 需要在 Info.plist 中添加使用说明,TouchID 不需要额外配置:

<!-- FaceID 使用说明(必须,否则调用时崩溃) -->
<key>NSFaceIDUsageDescription</key>
<string>需要使用 Face ID 验证您的身份,保护账户安全</string>

常见错误:不要添加 NSLocalAuthenticationUsageDescription 这个 key——它不是有效的 Info.plist 键,系统不会识别。只有 NSFaceIDUsageDescription 是需要配置的。也不要重复添加同一个 key(重复的 key 只有最后一个生效)。


2. 核心 API

LAContext

LAContext 是生物识别的上下文对象,每次认证建议创建新的实例(不要复用全局实例,避免状态残留)。

LAPolicy(两种验证策略)

策略 说明 适用场景
deviceOwnerAuthenticationWithBiometrics 只允许生物识别,失败后不自动弹出密码输入 严格要求生物识别的场景
deviceOwnerAuthentication 先生物识别,失败后可以选择输入设备密码 大多数场景,用户体验更好

推荐用 deviceOwnerAuthentication,用户生物识别失败后还能用密码解锁,体验更友好。

biometryType(生物识别类型)

说明
.none 不支持生物识别(或未调用 canEvaluatePolicy)
.faceID 支持 FaceID
.touchID 支持 TouchID
.opticID 支持 OpticID(iPhone 15 Pro+ 屏下指纹,iOS 17+)

注意:biometryType 必须在调用 canEvaluatePolicy:error: 之后才有正确的值,调用之前是 .none


3. 错误码处理

生物识别失败时,error.code 对应不同的错误类型:

错误码 说明
LAErrorAuthenticationFailed -1 认证失败(指纹/面部不匹配)
LAErrorUserCancel -2 用户取消
LAErrorUserFallback -3 用户选择输入密码(fallback)
LAErrorSystemCancel -4 系统取消(如来电、按 Home 键)
LAErrorPasscodeNotSet -5 设备未设置密码
LAErrorBiometryNotAvailable -6 生物识别不可用(设备不支持或被禁用)
LAErrorBiometryNotEnrolled -7 未录入指纹/面部
LAErrorBiometryLockout -8 生物识别被锁定(多次失败,需输入密码解锁)
LAErrorAppCancel -9 App 主动取消(调用 invalidate)

4. 完整封装(OC)

// BiometricAuthManager.h
#import <Foundation/Foundation.h>
#import <LocalAuthentication/LocalAuthentication.h>

typedef NS_ENUM(NSInteger, BiometricType) {
    BiometricTypeNone,
    BiometricTypeTouchID,
    BiometricTypeFaceID,
    BiometricTypeOpticID API_AVAILABLE(ios(17.0))
};

typedef void (^BiometricAuthCompletion)(BOOL success, NSString * _Nullable message, NSError * _Nullable error);

@interface BiometricAuthManager : NSObject

/// 检测设备支持的生物识别类型
+ (BiometricType)supportedBiometricType;

/// 发起生物识别认证
/// @param reason 提示原因(显示在验证框上)
/// @param allowFallback 是否允许 fallback 到密码
/// @param completion 回调(主线程)
+ (void)authenticateWithReason:(NSString *)reason
                  allowFallback:(BOOL)allowFallback
                     completion:(BiometricAuthCompletion)completion;

@end
// BiometricAuthManager.m
#import "BiometricAuthManager.h"

@implementation BiometricAuthManager

+ (BiometricType)supportedBiometricType {
    LAContext *context = [[LAContext alloc] init];
    NSError *error = nil;
    
    // 必须先调用 canEvaluatePolicy,biometryType 才有值
    if (![context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
        return BiometricTypeNone;
    }
    
    if (@available(iOS 17.0, *)) {
        if (context.biometryType == LABiometryTypeOpticID) {
            return BiometricTypeOpticID;
        }
    }
    if (context.biometryType == LABiometryTypeFaceID) {
        return BiometricTypeFaceID;
    }
    if (context.biometryType == LABiometryTypeTouchID) {
        return BiometricTypeTouchID;
    }
    return BiometricTypeNone;
}

+ (void)authenticateWithReason:(NSString *)reason
                  allowFallback:(BOOL)allowFallback
                     completion:(BiometricAuthCompletion)completion {
    
    // 每次认证创建新的 LAContext,不要复用
    LAContext *context = [[LAContext alloc] init];
    NSError *error = nil;
    
    // 选择验证策略
    LAPolicy policy = allowFallback ? LAPolicyDeviceOwnerAuthentication : LAPolicyDeviceOwnerAuthenticationWithBiometrics;
    
    // 检查是否支持
    if (![context canEvaluatePolicy:policy error:&error]) {
        dispatch_async(dispatch_get_main_queue(), ^{
            completion(NO, [self errorMessageForError:error], error);
        });
        return;
    }
    
    // 发起认证
    [context evaluatePolicy:policy localizedReason:reason reply:^(BOOL success, NSError * _Nullable error) {
        dispatch_async(dispatch_get_main_queue(), ^{
            if (success) {
                completion(YES, @"认证成功", nil);
            } else {
                completion(NO, [self errorMessageForError:error], error);
            }
        });
    }];
}

+ (NSString *)errorMessageForError:(NSError *)error {
    switch (error.code) {
        case LAErrorAuthenticationFailed:
            return @"认证失败,请重试";
        case LAErrorUserCancel:
            return @"用户取消了认证";
        case LAErrorUserFallback:
            return @"用户选择输入密码";
        case LAErrorSystemCancel:
            return @"系统取消了认证";
        case LAErrorPasscodeNotSet:
            return @"设备未设置密码,无法使用生物识别";
        case LAErrorBiometryNotAvailable:
            return @"设备不支持生物识别";
        case LAErrorBiometryNotEnrolled:
            return @"未录入指纹/面部,请在系统设置中添加";
        case LAErrorBiometryLockout:
            return @"生物识别已被锁定,请输入设备密码解锁后重试";
        case LAErrorAppCancel:
            return @"应用取消了认证";
        default:
            return [NSString stringWithFormat:@"认证失败:%@", error.localizedDescription];
    }
}

@end

5. 完整封装(Swift)

import LocalAuthentication

enum BiometricType {
    case none, touchID, faceID, opticID
}

class BiometricAuthManager {
    
    static var supportedType: BiometricType {
        let context = LAContext()
        var error: NSError?
        guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
            return .none
        }
        if #available(iOS 17.0, *) {
            if context.biometryType == .opticID { return .opticID }
        }
        switch context.biometryType {
        case .faceID: return .faceID
        case .touchID: return .touchID
        default: return .none
        }
    }
    
    static func authenticate(reason: String,
                             allowFallback: Bool = true,
                             completion: @escaping (Bool, String?, Error?) -> Void) {
        let context = LAContext() // 每次创建新实例
        var error: NSError?
        
        let policy: LAPolicy = allowFallback ? .deviceOwnerAuthentication : .deviceOwnerAuthenticationWithBiometrics
        
        guard context.canEvaluatePolicy(policy, error: &error) else {
            DispatchQueue.main.async {
                completion(false, errorMessage(for: error), error)
            }
            return
        }
        
        context.evaluatePolicy(policy, localizedReason: reason) { success, error in
            DispatchQueue.main.async {
                if success {
                    completion(true, "认证成功", nil)
                } else {
                    completion(false, errorMessage(for: error), error)
                }
            }
        }
    }
    
    private static func errorMessage(for error: Error?) -> String {
        guard let error = error as? LAError else {
            return error?.localizedDescription ?? "认证失败"
        }
        switch error.code {
        case .authenticationFailed: return "认证失败,请重试"
        case .userCancel: return "用户取消了认证"
        case .userFallback: return "用户选择输入密码"
        case .systemCancel: return "系统取消了认证"
        case .passcodeNotSet: return "设备未设置密码"
        case .biometryNotAvailable: return "设备不支持生物识别"
        case .biometryNotEnrolled: return "未录入指纹/面部"
        case .biometryLockout: return "生物识别已被锁定,请输入密码解锁"
        case .appCancel: return "应用取消了认证"
        default: return error.localizedDescription
        }
    }
}

二、手势识别(UIGestureRecognizer)

一句话原理

手势识别器是把一系列触摸事件(按下、移动、抬起)封装成高层语义(点击、滑动、捏合等),开发者不需要自己处理复杂的触摸逻辑,直接识别手势即可。

手势的状态机

每个手势识别器都有状态,随着触摸事件变化:

Possible(可能)→ Began(开始)→ Changed(变化中)→ Ended(结束)
                                      ↓
                                  Cancelled(取消)
                                      ↓
                                  Failed(失败)
状态 说明
Possible 初始状态,正在识别中
Began 手势开始识别(如手指开始移动)
Changed 手势持续变化中(如手指持续移动)
Ended 手势成功结束
Cancelled 手势被取消(如来电中断)
Failed 手势识别失败

离散手势(如点击)只有 Ended 状态;连续手势(如平移、捏合)有 Began → Changed → Ended 完整流程。


7 种手势识别器

手势类 说明 类型
UITapGestureRecognizer 点击(单击/双击/多击) 离散
UILongPressGestureRecognizer 长按 离散/连续
UIPinchGestureRecognizer 捏合(缩放) 连续
UIRotationGestureRecognizer 旋转 连续
UIPanGestureRecognizer 平移(拖动) 连续
UISwipeGestureRecognizer 轻扫(快速滑动) 离散
UIScreenEdgePanGestureRecognizer 屏幕边缘平移 连续

1. 点击手势(UITapGestureRecognizer)

UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];
tap.numberOfTapsRequired = 2;  // 双击(默认 1 单击)
tap.numberOfTouchesRequired = 1; // 单指(默认 1)
[view addGestureRecognizer:tap];

2. 长按手势(UILongPressGestureRecognizer)

UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress:)];
longPress.minimumPressDuration = 0.5; // 最短按 0.5 秒
longPress.allowableMovement = 10; // 允许移动 10 像素
[view addGestureRecognizer:longPress];

3. 捏合手势(UIPinchGestureRecognizer)

- (void)handlePinch:(UIPinchGestureRecognizer *)pinch {
    if (pinch.state == UIGestureRecognizerStateChanged) {
        pinch.view.transform = CGAffineTransformScale(pinch.view.transform, pinch.scale, pinch.scale);
        pinch.scale = 1.0; // 重置,避免累积
    }
}

4. 旋转手势(UIRotationGestureRecognizer)

- (void)handleRotation:(UIRotationGestureRecognizer *)rotation {
    if (rotation.state == UIGestureRecognizerStateChanged) {
        rotation.view.transform = CGAffineTransformRotate(rotation.view.transform, rotation.rotation);
        rotation.rotation = 0; // 重置
    }
}

5. 平移手势(UIPanGestureRecognizer)

- (void)handlePan:(UIPanGestureRecognizer *)pan {
    CGPoint translation = [pan translationInView:pan.view.superview];
    if (pan.state == UIGestureRecognizerStateChanged) {
        pan.view.center = CGPointMake(pan.view.center.x + translation.x, pan.view.center.y + translation.y);
        [pan setTranslation:CGPointZero inView:pan.view.superview]; // 重置
    }
}

6. 轻扫手势(UISwipeGestureRecognizer)

UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipe:)];
swipe.direction = UISwipeGestureRecognizerDirectionLeft; // 向左轻扫
[view addGestureRecognizer:swipe];

7. 屏幕边缘平移(UIScreenEdgePanGestureRecognizer)

UIScreenEdgePanGestureRecognizer *edgePan = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(handleEdgePan:)];
edgePan.edges = UIRectEdgeLeft; // 从左边缘开始
[view addGestureRecognizer:edgePan];

手势冲突处理

问题场景

一个 view 上同时添加了点击手势和双击手势,单击时会先触发单击,再触发双击——但我们希望双击时不触发单击。

解决方案一:requireGestureRecognizerToFail

// 单击手势需要等双击手势失败后才触发
[tap requireGestureRecognizerToFail:doubleTap];

解决方案二:手势代理

@interface ViewController () <UIGestureRecognizerDelegate>
@end

// 允许多个手势同时识别
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return YES; // 捏合和旋转可以同时进行
}

// 某个手势开始前是否可以识别
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    return YES;
}

手势与 UIButton 冲突

UIButton 自带点击事件,如果给 button 的 superview 添加了 tap 手势,点击 button 时可能两个都触发。解决方法:

// 手势识别器忽略对 UIControl 子视图的触摸
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    if ([touch.view isKindOfClass:[UIControl class]]) {
        return NO; // 不拦截按钮的点击
    }
    return YES;
}

手势工具类封装(OC)

// GestureHelper.h
#import <UIKit/UIKit.h>

typedef void (^GestureBlock)(UIGestureRecognizer *gesture);

@interface GestureHelper : NSObject

/// 添加点击手势(block 回调)
+ (UITapGestureRecognizer *)addTapToView:(UIView *)view
                              tapsRequired:(NSUInteger)taps
                                    action:(GestureBlock)action;

/// 添加长按手势
+ (UILongPressGestureRecognizer *)addLongPressToView:(UIView *)view
                                            duration:(NSTimeInterval)duration
                                              action:(GestureBlock)action;

/// 添加平移手势
+ (UIPanGestureRecognizer *)addPanToView:(UIView *)view action:(GestureBlock)action;

/// 添加捏合手势
+ (UIPinchGestureRecognizer *)addPinchToView:(UIView *)view action:(GestureBlock)action;

/// 添加旋转手势
+ (UIRotationGestureRecognizer *)addRotationToView:(UIView *)view action:(GestureBlock)action;

/// 添加轻扫手势
+ (UISwipeGestureRecognizer *)addSwipeToView:(UIView *)view
                                   direction:(UISwipeGestureRecognizerDirection)direction
                                      action:(GestureBlock)action;

@end
// GestureHelper.m
#import "GestureHelper.h"
#import <objc/runtime.h>

static const void *kGestureBlockKey = &kGestureBlockKey;

@implementation GestureHelper

+ (UITapGestureRecognizer *)addTapToView:(UIView *)view tapsRequired:(NSUInteger)taps action:(GestureBlock)action {
    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
    tap.numberOfTapsRequired = taps;
    [self attachBlock:action toGesture:tap];
    [view addGestureRecognizer:tap];
    return tap;
}

+ (UILongPressGestureRecognizer *)addLongPressToView:(UIView *)view duration:(NSTimeInterval)duration action:(GestureBlock)action {
    UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
    longPress.minimumPressDuration = duration;
    [self attachBlock:action toGesture:longPress];
    [view addGestureRecognizer:longPress];
    return longPress;
}

+ (UIPanGestureRecognizer *)addPanToView:(UIView *)view action:(GestureBlock)action {
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
    [self attachBlock:action toGesture:pan];
    [view addGestureRecognizer:pan];
    return pan;
}

+ (UIPinchGestureRecognizer *)addPinchToView:(UIView *)view action:(GestureBlock)action {
    UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
    [self attachBlock:action toGesture:pinch];
    [view addGestureRecognizer:pinch];
    return pinch;
}

+ (UIRotationGestureRecognizer *)addRotationToView:(UIView *)view action:(GestureBlock)action {
    UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
    [self attachBlock:action toGesture:rotation];
    [view addGestureRecognizer:rotation];
    return rotation;
}

+ (UISwipeGestureRecognizer *)addSwipeToView:(UIView *)view direction:(UISwipeGestureRecognizerDirection)direction action:(GestureBlock)action {
    UISwipeGestureRecognizer *swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
    swipe.direction = direction;
    [self attachBlock:action toGesture:swipe];
    [view addGestureRecognizer:swipe];
    return swipe;
}

#pragma mark - Private

+ (void)attachBlock:(GestureBlock)block toGesture:(UIGestureRecognizer *)gesture {
    objc_setAssociatedObject(gesture, kGestureBlockKey, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

+ (void)handleGesture:(UIGestureRecognizer *)gesture {
    GestureBlock block = objc_getAssociatedObject(gesture, kGestureBlockKey);
    if (block) block(gesture);
}

@end

用 block 回调比 target/action 更灵活,不需要在控制器中写一堆 @objc 方法。用 objc_setAssociatedObject 把 block 关联到手势对象上。


三、使用示例

OC 示例

#import "ViewController.h"
#import "BiometricAuthManager.h"
#import "GestureHelper.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *demoView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 1. 生物识别
    BiometricType type = [BiometricAuthManager supportedBiometricType];
    NSLog(@"支持的生物识别:%ld", (long)type);
    
    [BiometricAuthManager authenticateWithReason:@"验证身份以查看敏感信息"
                                    allowFallback:YES
                                       completion:^(BOOL success, NSString *message, NSError *error) {
        NSLog(@"%@", message);
        if (success) {
            // 显示敏感内容
        }
    }];
    
    // 2. 点击手势(block 回调)
    [GestureHelper addTapToView:self.demoView tapsRequired:1 action:^(UIGestureRecognizer *gesture) {
        NSLog(@"点击了 view");
    }];
    
    // 3. 长按手势
    [GestureHelper addLongPressToView:self.demoView duration:0.5 action:^(UIGestureRecognizer *gesture) {
        if (gesture.state == UIGestureRecognizerStateBegan) {
            NSLog(@"长按开始");
        }
    }];
    
    // 4. 平移手势(拖动 view)
    [GestureHelper addPanToView:self.demoView action:^(UIGestureRecognizer *gesture) {
        UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)gesture;
        CGPoint translation = [pan translationInView:self.view];
        if (pan.state == UIGestureRecognizerStateChanged) {
            pan.view.center = CGPointMake(pan.view.center.x + translation.x, pan.view.center.y + translation.y);
            [pan setTranslation:CGPointZero inView:self.view];
        }
    }];
    
    // 5. 捏合 + 旋转(同时识别)
    UIPinchGestureRecognizer *pinch = [GestureHelper addPinchToView:self.demoView action:^(UIGestureRecognizer *gesture) {
        UIPinchGestureRecognizer *p = (UIPinchGestureRecognizer *)gesture;
        if (p.state == UIGestureRecognizerStateChanged) {
            p.view.transform = CGAffineTransformScale(p.view.transform, p.scale, p.scale);
            p.scale = 1.0;
        }
    }];
    
    UIRotationGestureRecognizer *rotation = [GestureHelper addRotationToView:self.demoView action:^(UIGestureRecognizer *gesture) {
        UIRotationGestureRecognizer *r = (UIRotationGestureRecognizer *)gesture;
        if (r.state == UIGestureRecognizerStateChanged) {
            r.view.transform = CGAffineTransformRotate(r.view.transform, r.rotation);
            r.rotation = 0;
        }
    }];
    
    // 允许捏合和旋转同时识别
    pinch.delegate = self;
    rotation.delegate = self;
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return YES;
}

@end

Swift 示例

class ViewController: UIViewController {
    @IBOutlet weak var demoView: UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 生物识别
        BiometricAuthManager.authenticate(reason: "验证身份") { success, message, error in
            print(message ?? "")
        }
        
        // 点击手势
        let tap = UITapGestureRecognizer { [weak self] _ in
            print("点击了")
        }
        demoView.addGestureRecognizer(tap)
        
        // 平移手势
        let pan = UIPanGestureRecognizer { [weak self] gesture in
            guard let pan = gesture as? UIPanGestureRecognizer else { return }
            let translation = pan.translation(in: self?.view)
            if pan.state == .changed {
                pan.view?.center.x += translation.x
                pan.view?.center.y += translation.y
                pan.setTranslation(.zero, in: self?.view)
            }
        }
        demoView.addGestureRecognizer(pan)
    }
}

// UIGestureRecognizer 的 block 扩展
extension UIGestureRecognizer {
    convenience init(action: @escaping (UIGestureRecognizer) -> Void) {
        self.init()
        self.actionBlock = action
        addTarget(self, action: #selector(handleAction(_:)))
    }
    
    private static var blockKey: UInt8 = 0
    var actionBlock: ((UIGestureRecognizer) -> Void)? {
        get { objc_getAssociatedObject(self, &Self.blockKey) as? (UIGestureRecognizer) -> Void }
        set { objc_setAssociatedObject(self, &Self.blockKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    }
    
    @objc private func handleAction(_ sender: UIGestureRecognizer) {
        actionBlock?(sender)
    }
}

四、常见问题与坑

Q1:调用生物识别时崩溃了?

检查 Info.plist 中是否添加了 NSFaceIDUsageDescription。FaceID 设备上没有这个 key 会直接崩溃。TouchID 不需要配置。

Q2:biometryType 总是返回 none?

biometryType 必须在调用 canEvaluatePolicy:error: 之后才有正确的值。直接访问 context.biometryType 而不先调用 canEvaluatePolicy,会得到 .none

Q3:LAContext 可以复用吗?

不建议。每次认证创建新的 LAContext 实例。复用可能导致 biometryType 缓存、上次认证状态残留、被系统 invalidate 后无法继续使用等问题。

Q4:生物识别被锁定了怎么办?

多次失败后生物识别会被锁定(LAErrorBiometryLockout),此时需要用户输入设备密码解锁。用 LAPolicyDeviceOwnerAuthentication 策略会自动提供密码输入选项。

Q5:捏合和旋转手势不能同时生效?

默认两个手势互斥,一个识别成功后另一个会失败。实现 UIGestureRecognizerDelegategestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: 方法返回 YES,允许多个手势同时识别。

Q6:单击和双击冲突怎么办?

requireGestureRecognizerToFail:,让单击手势等双击手势失败后再触发:

[singleTap requireGestureRecognizerToFail:doubleTap];

Q7:手势和 UIButton 点击冲突?

给父视图添加的 tap 手势会拦截 button 的点击。用手势代理的 gestureRecognizer:shouldReceiveTouch: 方法,当触摸的 view 是 UIControl 时返回 NO,不拦截。

Q8:UISwipeGestureRecognizer 和 UIPanGestureRecognizer 冲突?

轻扫是快速滑动,平移是慢速拖动。可以让轻扫手势等平移手势失败,或者通过速度/位移判断。一般轻扫用于快速切换页面,平移用于拖动,根据场景选择。

Q9:手势的 block 会循环引用吗?

block 中引用了 self,而手势对象持有 block,view 持有手势,self 持有 view——形成循环引用。在 block 中用 __weak typeof(self) weakSelf = self 打破循环。

Q10:生物识别的结果可以被伪造吗?

不能。验证过程在 Secure Enclave 中完成,App 只能收到系统返回的成功/失败结果,无法伪造。但要注意:生物识别只能验证"这是设备主人",不能替代服务端的身份验证(如 token 校验)。


五、总结

  • 生物识别
    • 原理:Secure Enclave 本地验证,App 拿不到生物数据。
    • 配置:只需要 NSFaceIDUsageDescription(FaceID),TouchID 不需要。
    • 策略:推荐 deviceOwnerAuthentication(允许 fallback 密码)。
    • 错误码:9 种常见错误,逐一处理给用户友好提示。
    • 注意:每次创建新的 LAContext,先 canEvaluatePolicy 再读 biometryType。
  • 手势识别
    • 7 种手势:点击、长按、捏合、旋转、平移、轻扫、边缘平移。
    • 状态机:Possible → Began → Changed → Ended/Cancelled/Failed。
    • 冲突处理:requireGestureRecognizerToFail、手势代理、shouldReceiveTouch。
    • 封装:用 block 回调替代 target/action,更灵活。
  • 设计原则:生物识别和手势识别职责不同,建议分开封装为两个工具类,不要混在一个类中。

posted @ 2024-07-16 16:17  Mr.陳  阅读(386)  评论(0)    收藏  举报