iOS-UI之遮罩效果(2)——图片遮罩(刮刮乐效果)
来看看实现步骤
1.先放一张图片,再加一层遮罩
将图片放在一个背景view上作为容器,bgView的颜色就是涂层颜色
let view = UIView()
view.backgroundColor = .lightGray
bgView.frame = CGRect(x: 0, y: 100, width: kScreenWidth, height: kScreenWidth * 9 / 16)
view.addSubview(bgView)
let imageView = UIImageView()
imageView.image = UIImage(named: "underworld.jpg")
imageView.isUserInteractionEnabled = true
imageView.frame = bgView.bounds
bgView.addSubview(imageView)
let bgMaskLayer = CALayer()
imageView.layer.mask = bgMaskLayer
效果图就是一片灰色
如果要在涂层未刮开的地方显示一个label,就一定要把label放到图片下面,这样刮开涂层的时候文字也会消失
如果是按钮的话,就要放在图片上面,并且要在刮涂层之前将按钮移除,以免挡住涂层
2.给图片添加平移手势
// 务必要将imageView的isUserInteractionEnabled属性设置为true
let pan = UIPanGestureRecognizer(target: self, action: #selector(panAction(_:)))
imageView.addGestureRecognizer(pan)
手势方法
@objc func panAction(_ pan:UIPanGestureRecognizer) {
let point = pan.location(in: imageView)
let maskLayer = CAShapeLayer()
maskLayer.lineWidth = 50
maskLayer.lineCap = .round
maskLayer.lineJoin = .round
maskLayer.strokeColor = UIColor.lightGray.cgColor
maskLayer.fillColor = nil
let maskPath = UIBezierPath()
switch pan.state {
case .began:
debugPrint("pan start")
currentPoint = point
maskPath.move(to: currentPoint)
case .changed:
debugPrint("pan -------")
maskPath.addLine(to: point)
currentPoint = point
case .ended:
debugPrint("pan end")
case .cancelled:
debugPrint("pan cancel")
default:
break
}
maskPath.move(to: currentPoint)
maskPath.addLine(to: point)
maskLayer.path = maskPath.cgPath
bgMaskLayer.addSublayer(maskLayer)
}
其中currentPoint是一个本类的的属性,用来记录手势划过的点
var currentPoint = CGPoint.zero
其原理就是在平移手势的方法中初始化一个CAShapeLayer,然后通过手势将其轨迹获取到并绘制出来,然后添加到bgMaskLayer的sublayer中
到这里,刮刮乐效果已经基本实现,效果如下
但是,当手指滑动过快时,问题就出现了:
滑动过快,也许由于手势方法执行速度的问题,绘制出来的Layer不够连续,就会出现上面的结果。
在这种实现方式下我没有太好的解决办法,所以有另一种实现方式:
3.另一种实现方式:
通过重写
touchesBegan(:with:)
touchesMoved(:with:)
touchesEnded(_:with:)
三个方法,来绘制layer
前面的准备和第一种方法类似
添加如下两个类属性
var lastPoint = CGPoint.zero
var isSwiping = false
重写方法
// 这里主要是来记录起始点
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
isSwiping = false
let touch = (touches as NSSet).anyObject() as! UITouch
lastPoint = touch.location(in: imageView2)
}
// 绘制layer的主体,有两种方法来添加layer的path,一种是通过context,另一种是用UIBezierPath,都是为了给layer设置path
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
isSwiping = true
let touch = (touches as NSSet).anyObject() as! UITouch
let curPoint = touch.location(in: imageView2)
// 两种方式添加轨迹
// UIGraphicsBeginImageContext(imageView2.frame.size)
// let context = UIGraphicsGetCurrentContext()
// context?.move(to: lastPoint)
// context?.addLine(to: curPoint)
// let maskLayer = CAShapeLayer()
// maskLayer.lineWidth = 50
// maskLayer.lineCap = .round
// maskLayer.lineJoin = .round
// maskLayer.strokeColor = UIColor.lightGray.cgColor
// maskLayer.fillColor = nil
// maskLayer.path = context!.path
// bgMaskLayer2.addSublayer(maskLayer)
// context?.closePath()
let maskLayer = CAShapeLayer()
maskLayer.lineWidth = 50
maskLayer.lineCap = .round
maskLayer.lineJoin = .round
maskLayer.strokeColor = UIColor.lightGray.cgColor
maskLayer.fillColor = nil
let maskPath = UIBezierPath()
maskPath.move(to: lastPoint)
maskPath.addLine(to: curPoint)
maskLayer.path = maskPath.cgPath
bgMaskLayer2.addSublayer(maskLayer)
lastPoint = curPoint
}
// 起始不重写这个方法也可以实现效果,不过在单点的时候就有可能会出现无法生成layer的问题,所以可以加上这个方法
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if !isSwiping { // 这样写的目的是为了解决轻点时无效果的问题
UIGraphicsBeginImageContext(imageView2.frame.size)
let context = UIGraphicsGetCurrentContext()
context?.move(to: lastPoint)
context?.addLine(to: lastPoint)
let maskLayer = CAShapeLayer()
maskLayer.lineWidth = 50
maskLayer.lineCap = .round
maskLayer.lineJoin = .round
maskLayer.strokeColor = UIColor.lightGray.cgColor
maskLayer.fillColor = nil
maskLayer.path = context!.path
bgMaskLayer2.addSublayer(maskLayer)
context?.closePath()
}
}
最后效果如下
这样就丝滑了许多
当划快了也不会出现一坨一坨的情况