Featured image of post TTGPuzzleVerify 2.0.0:拼图验证控件的 Swift 化和能力补齐

TTGPuzzleVerify 2.0.0:拼图验证控件的 Swift 化和能力补齐

前言

TTGPuzzleVerify 是一个拼图验证控件。最早写它的时候,重点是“怎么从图片里抠出一块拼图、怎么做镂空、怎么判断拖动是否成功”。那篇旧文章里也主要讲的是 UIBezierPathCAShapeLayer.mask 和手势判断。

这次更新到 2.0.0,核心思路没有变,但项目本身变得完整了很多:Swift 原生实现、Objective-C 可调用、SwiftUI 可包装、统一配置对象、更多验证行为、状态机、失败反馈、结果指标等等。

TTGPuzzleVerify Poster

Github: https://github.com/zekunyan/TTGPuzzleVerify

更新

2.0.0 这次主要做了这些事情:

  • 迁移到 Swift:核心类 TTGPuzzleVerifyView 用 Swift 实现,同时通过 @objc 暴露给 Objective-C。
  • 统一配置:新增 TTGPuzzleVerifyConfiguration,把形状、尺寸、容差、拖动方向、自动吸附、重试次数等放到一起。
  • 更多形状:内置 classic、square、circle,并支持自定义 UIBezierPath
  • 拖动方向控制:支持水平、垂直、双轴拖动。
  • 状态机:公开 idle、dragging、verified、failed、locked 状态。
  • 结果指标:成功/失败回调里带上偏移、耗时、拖动距离、交互次数等数据。
  • SwiftUI / UIKit / Objective-C 共用核心:不同集成方式不再各写一套逻辑。

Architecture

快速使用

UIKit 里最基本的配置如下:

import TTGPuzzleVerify

let puzzleView = TTGPuzzleVerifyView()
puzzleView.image = UIImage(named: "background")

let configuration = TTGPuzzleVerifyConfiguration()
configuration.puzzlePattern = .circlePattern
configuration.puzzleSize = CGSize(width: 92, height: 92)
configuration.allowedAxes = .horizontal
configuration.verificationTolerance = 7

puzzleView.applyConfiguration(configuration)
puzzleView.puzzleBlankPosition = CGPoint(x: 210, y: 20)
puzzleView.resetVerification()

Quick Start

如果在 SwiftUI 里用,也就是包一层 UIViewRepresentable

struct PuzzleVerifyRepresentable: UIViewRepresentable {
    @Binding var isVerified: Bool

    func makeUIView(context: Context) -> TTGPuzzleVerifyView {
        let view = TTGPuzzleVerifyView()
        view.image = UIImage(named: "background")

        let configuration = TTGPuzzleVerifyConfiguration()
        configuration.puzzlePattern = .classicPattern
        configuration.puzzleSize = CGSize(width: 86, height: 86)
        configuration.allowedAxes = .both
        configuration.autoSnapWhenWithinTolerance = true

        view.applyConfiguration(configuration)
        view.puzzleBlankPosition = CGPoint(x: 220, y: 96)
        view.verificationChangeBlock = { _, verified in
            isVerified = verified
        }
        return view
    }
}

配置对象

旧版很多属性是直接设置到 view 上的,这当然简单,但当行为越来越多以后,调用处会比较散。新版增加了 TTGPuzzleVerifyConfiguration

let configuration = TTGPuzzleVerifyConfiguration()
configuration.puzzlePattern = .classicPattern
configuration.puzzleSize = CGSize(width: 100, height: 100)
configuration.verificationTolerance = 8
configuration.allowedAxes = .both
configuration.autoSnapWhenWithinTolerance = true
configuration.recordsTrack = true
configuration.maxRetryCount = 3

puzzleView.applyConfiguration(configuration)

这里最重要的几个参数:

  • puzzlePattern:拼图形状。
  • puzzleSize:拼图块大小。
  • verificationTolerance:验证容差,避免要求用户像素级对齐。
  • allowedAxes:限制水平、垂直或双轴拖动。
  • autoSnapWhenWithinTolerance:进入容差范围时自动吸附到正确位置。
  • maxRetryCount:失败次数达到上限后进入 locked 状态。

这个配置对象的好处是,产品层可以维护几套不同策略:比如登录页更严格,活动页更轻一点,或者调试环境打开 track 记录。

状态和结果

拼图验证不只是“成功/失败”两个布尔值。实际接到业务里以后,常常还需要知道失败了几次、拖动过程是否异常、耗时多久、偏差是多少。

新版公开了状态:

public enum TTGPuzzleVerifyState: Int {
    case idle
    case dragging
    case verified
    case failed
    case locked
}

完成时会产生 TTGPuzzleVerifyResult,里面包括:

  • isVerified
  • puzzlePosition
  • blankPosition
  • xOffset / yOffset
  • elapsedTime
  • dragDistance
  • interactionCount

注意,这些指标不是为了在客户端做“安全风控”,客户端本身不适合承担这个职责。它更适合用于体验分析、调试、轻量行为判断,真正的安全策略还是应该服务端参与。

实现回顾

拼图控件核心还是三个层:

  • 背景图:完整图片。
  • 镂空图:用 even-odd mask 从背景中抠掉拼图区域。
  • 拼图块:同一张图片通过 puzzle path mask 裁出来。

旧文章里讲过 mask 的基本思路,这次重构后,重点是把这些能力封装成可以稳定复用的状态机:配置变化时更新 path 和 mask,拖动时更新拼图位置,结束时根据容差和轴向策略决定 verified/failed/locked。

在 API 上,customPuzzlePatternPath 仍然保留给自定义形状:

configuration.puzzlePattern = .customPattern
puzzleView.customPuzzlePatternPath = myBezierPath
puzzleView.applyConfiguration(configuration)

这样内置形状不够时,外部可以继续提供自己的 UIBezierPath,控件本身只负责把路径缩放、移动、应用到 mask 和验证逻辑里。

总结

TTGPuzzleVerify 2.0.0 更像是把一个“有趣的拼图 Demo 控件”整理成了一个能在项目里使用的组件:Swift 化、配置化、状态清晰、回调信息更完整,也兼顾了 UIKit、SwiftUI 和 Objective-C。

当然,拼图验证这种东西也要看使用场景。它可以增加交互门槛、减少误触或低成本刷操作,但不要把它当成完整安全方案。控件负责交互体验,安全策略还是要回到整体系统设计里。