50行代码以内实现刮刮乐

3,116 阅读2分钟

1. 简介

不搞哪些花里胡哨的,如何利用系统 API 快速实现刮刮乐。

ScratchCard

2. 实战

思路:通过蒙版实现刮的效果。刮的过程,可以转换为一个画图的过程。

2.1 子类化 UIPanGestureRecognizer

class SNXPanGestureRecognizer: UIPanGestureRecognizer {
    var touchBeganLocation: CGPoint?
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        super.touchesBegan(touches, with: event)
        touchBeganLocation = touches.first?.location(in: view)
    }
}

为什么要子类化 UIPanGestureRecognizer 呢?

因为 UIPanGestureRecognizer 在识别的时候有一个识别距离,如果从 UIGestureRecognizer.State.began 才开始记录,就会丢失掉一个关键点,导致用户觉得不是从自己开始刮的地方开始的。

2.2 刮刮乐视图

创建两层视图:

  • coverView 在底层,可以用纯色,或者喜欢的刮刮乐封面图片。
  • imageView 在上层。因为后面会用透明的蒙层,所以会看不到里面的内容,视觉上就像只有刮刮乐封面一样。当我们绘制了图案后,图案部分会被展示出来,遮挡 coverView 就像被刮出来了一样。
// 封面视图
let coverView: UIView = UIView()
coverView.backgroundColor = UIColor.gray
coverView.frame = CGRect(x: 0, y: 0, width: 350, height: 235)
coverView.center = view.center
view.addSubview(coverView)
// 一等奖图片
let imageView: UIImageView = UIImageView()
imageView.frame = coverView.frame
imageView.image = UIImage(named: "prize")
imageView.contentMode = .scaleAspectFill
view.addSubview(imageView)

蒙层的代码也很简单。配置好需要的参数后,为 imageView 添加上 mask 就行了。

// maskLayer 为 VC 的实例变量,后面还要在 action 回调里面使用
let maskLayer: CAShapeLayer = CAShapeLayer()

// 蒙层
maskLayer.frame = imageView.bounds
maskLayer.lineWidth = 8.0
maskLayer.lineCap = CAShapeLayerLineCap.round
maskLayer.lineJoin = CAShapeLayerLineJoin.round
maskLayer.strokeColor = UIColor.black.cgColor
maskLayer.fillColor = UIColor.clear.cgColor
imageView.layer.mask = maskLayer

手势我们加在了 coverView 上。

imageView 默认是不响应手势的,所以这里我加在了 coverView 上。

这里可以根据个人喜好,添加到自己喜欢的视图上。

// 手势
let panGes: SNXPanGestureRecognizer = SNXPanGestureRecognizer(target: self, action: #selector(panGesHandler(_:)))
coverView.addGestureRecognizer(panGes)

响应代码也很简单。

UIGestureRecognizer.State.began 时移动到用户开始的触摸点,然后 addLine(to:) 手势识别的坐标,就把用户开始的部分补上了。之后不断调用 addLine(to:) 即可完成绘制。最后再将 path 更新到 maskLayer 上就完成了。

// path 为 VC 的实例变量
let path: UIBezierPath = UIBezierPath()

@objc func panGesHandler(_ recognizer: SNXPanGestureRecognizer) {
    switch recognizer.state {
    case .began:
        if let touchBeganLocation = recognizer.touchBeganLocation {
            path.move(to: touchBeganLocation)
        }
        fallthrough
    case .changed, .ended, .cancelled:
        path.addLine(to: recognizer.location(in: recognizer.view))
    default:
        break
    }
    maskLayer.path = path.cgPath
}

2.3 完整代码

class ViewController: UIViewController {
    let maskLayer: CAShapeLayer = CAShapeLayer()
    let path: UIBezierPath = UIBezierPath()

    override func viewDidLoad() {
        super.viewDidLoad()
        // 封面视图
        let coverView: UIView = UIView()
        coverView.backgroundColor = UIColor.gray
        coverView.frame = CGRect(x: 0, y: 0, width: 350, height: 235)
        coverView.center = view.center
        view.addSubview(coverView)
        // 一等奖图片
        let imageView: UIImageView = UIImageView()
        imageView.frame = coverView.frame
        imageView.image = UIImage(named: "prize")
        imageView.contentMode = .scaleAspectFill
        view.addSubview(imageView)
        // 蒙层
        maskLayer.frame = imageView.bounds
        maskLayer.lineWidth = 8.0
        maskLayer.lineCap = CAShapeLayerLineCap.round
        maskLayer.lineJoin = CAShapeLayerLineJoin.round
        maskLayer.strokeColor = UIColor.black.cgColor
        maskLayer.fillColor = UIColor.clear.cgColor
        imageView.layer.mask = maskLayer
        // 手势
        let panGes: SNXPanGestureRecognizer = SNXPanGestureRecognizer(target: self, action: #selector(panGesHandler(_:)))
        coverView.addGestureRecognizer(panGes)
    }

    @objc func panGesHandler(_ recognizer: SNXPanGestureRecognizer) {
        switch recognizer.state {
        case .began:
            if let touchBeganLocation = recognizer.touchBeganLocation {
                path.move(to: touchBeganLocation)
            }
            fallthrough
        case .changed, .ended, .cancelled:
            path.addLine(to: recognizer.location(in: recognizer.view))
        default:
            break
        }
        maskLayer.path = path.cgPath
    }
}

如果觉得本文不错,给我点个赞吧~❤️