iOS:一用就上瘾的刮刮乐视图

3,746 阅读4分钟

彩票刮刮乐.png

Github仓库地址

仓库地址,喜欢就star一下❤️

前言

这是一个简单却功能强大的刮刮乐视图,几行代码就可以实现刮刮乐效果,而且性能良好。下面有美女福利哟,相信我,你会喜欢的😍😍

相信大家都买过彩票刮刮乐,总是会抱着中大奖的情况去刮,希望自己是最幸运的那一个,刮中五百万,抱得美人归,从此走上人生巅峰。但现实往往是你口袋里面的几十块零钱,几分钟就被消费殆尽了😂 许多APP也集成了这一功能,比如用支付宝线下支付后就有刮刮乐。虽然刮中的都是些没多大用的优惠券,但总是会吸引人去刮一刮,万一中了大奖呢😎

实现效果

多说无益,先来看看实现的效果吧

彩票刮刮乐

彩票刮刮乐.gif

参照了一个叫做“撕掉她的衣服”APP,效果非常sexy,有没有一种心跳加快,血脉膨胀的感觉。(相信大家迫不及待想要体验一下了,点击fir.im/JXScratchVi…,通过safari打开该链接,安装之后信任证书,就可以快速体验了)相信我在空白处,双击一下,你会发现新大陆😍

调研

在网上搜索了一番,方案基本上就是这种:链接。 核心代码:

-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    // 触摸任意位置
    UITouch *touch = touches.anyObject;
    // 触摸位置在图片上的坐标
    CGPoint cententPoint = [touch locationInView:self.imageView];
    // 设置清除点的大小
    CGRect  rect = CGRectMake(cententPoint.x, cententPoint.y, 20, 20);
    // 默认是去创建一个透明的视图
    UIGraphicsBeginImageContextWithOptions(self.imageView.bounds.size, NO, 0);
    // 获取上下文(画板)
    CGContextRef ref = UIGraphicsGetCurrentContext();
    // 把imageView的layer映射到上下文中
    [self.imageView.layer renderInContext:ref];
    // 清除划过的区域
    CGContextClearRect(ref, rect);
    // 获取图片
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    // 结束图片的画板, (意味着图片在上下文中消失)
    UIGraphicsEndImageContext();
    
    self.imageView.image = image;
}

缺点很明显: 1、画笔是矩形的,看着很难受; 2、绘画的核心代码用了CoreGraphics,每次移动都要重新开启一个context上下文,绘制image,对CPU的性能有很大的消耗。点击该链接,详细了解CAShapeLayer比CG的优势 所以,我想了一个骚技巧,用CAShapeLayer作为mask来实现该效果。点击该链接,了解mask图层遮罩

原理

图层解释.png 如图所示,只要用户滑动的时候,更新contentView的maskLayer的path,就可以实现刮刮乐的效果了。代码如下:

  • 初始化
/// 指定初始化器
    ///
    /// - Parameters:
    ///   - contentView: 内容视图,比如彩票的奖品详情内容。(需要隐藏起来的内容)
    ///   - maskView: 遮罩视图
    public init(contentView: UIView, maskView: UIView) {
        super.init(frame: CGRect.zero)

        scratchMaskView = maskView
        self.addSubview(scratchMaskView)

        scratchContentView = contentView
        self.addSubview(scratchContentView)

        maskLayer = CAShapeLayer()
        maskLayer.strokeColor = UIColor.red.cgColor
        maskLayer.lineWidth = strokeLineWidth
        maskLayer.lineCap = strokeLineCap
        scratchContentView?.layer.mask = maskLayer

        maskPath = UIBezierPath()
    }
  • 绘画核心代码
open override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else {
            return
        }
        let point = touch.location(in: scratchContentView)
        maskPath.move(to: point)
    }

    open override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else {
            return
        }
        let point = touch.location(in: scratchContentView)
        maskPath.addLine(to: point)
        maskPath.move(to: point)
        maskLayer.path = maskPath.cgPath
    }
  • 获取已经刮了多少百分比,比如用户刮了70%的时候,就显示全部。
//获取透明像素占总像素的百分比
    private func getAlphaPixelPercent(img: UIImage) -> Float {
        //计算像素总个数
        let width = Int(img.size.width)
        let height = Int(img.size.height)
        let bitmapByteCount = width * height

        //得到所有像素数据
        let pixelData = UnsafeMutablePointer<UInt8>.allocate(capacity: bitmapByteCount)
        let colorSpace = CGColorSpaceCreateDeviceGray()
        let context = CGContext(data: pixelData,
                                width: width,
                                height: height,
                                bitsPerComponent: 8,
                                bytesPerRow: width,
                                space: colorSpace,
                                bitmapInfo: CGBitmapInfo(rawValue:
                                    CGImageAlphaInfo.alphaOnly.rawValue).rawValue)!
        let rect = CGRect(x: 0, y: 0, width: width, height: height)
        context.clear(rect)
        context.draw(img.cgImage!, in: rect)


        //计算透明像素个数
        var alphaPixelCount = 0
        for x in 0...Int(width) {
            for y in 0...Int(height) {
                if pixelData[y * width + x] == 0 {
                    alphaPixelCount += 1
                }
            }
        }

        free(pixelData)

        return Float(alphaPixelCount) / Float(bitmapByteCount)
    }
    //展示全部
    open func showContentView() {
        self.scratchContentView.layer.mask = nil
    }

使用

  • 彩票刮刮乐示例代码
        let contentView = UILabel()
        contentView.backgroundColor = UIColor.white
        contentView.textAlignment = .center
        contentView.font = UIFont.systemFont(ofSize: 25)
        contentView.text = "恭喜你刮中500万"
        contentView.numberOfLines = 0

        let maskView = UIView()
        maskView.backgroundColor = UIColor.lightGray

        let ratio = self.bounds.size.width/400
        scratchView = JXScratchView(contentView: contentView, maskView: maskView)
        scratchView.delegate = self
        scratchView.strokeLineWidth = 25
        scratchView.strokeLineCap = kCALineCapRound
        scratchView.frame = CGRect(x: 33*ratio, y: 140*ratio, width: 337*ratio, height: 154*ratio)
        addSubview(scratchView)
  • 指定使用JXScratchView的public init(contentView: UIView, maskView: UIView)初始化器,只需要传入UIView及其子类就可以了。
  • strokeLineCap属性设置stroke形状,默认kCALineCapRound
  • strokeLineWidth属性设置stroke线宽,默认20
  • 遵从JXScratchViewDelegate,实现func scratchView(scratchView: JXScratchView, didScratched percent: Float)代理方法,就可以实时获取刮刮乐的百分比。
  • 建议新建一个UIView,把JXScratchView封装进去,可以参考JXScratchTicketView

Github仓库地址

仓库地址,喜欢就star一下❤️