[英]How do I mask and erase a CAShapeLayer?
我正在嘗試使擦除功能與 CAShapeLayer 一起使用。 當前代碼:
class MaskTestVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let image = UIImage(named: "test.jpg")!
let testLayer = CAShapeLayer()
testLayer.contents = image.cgImage
testLayer.frame = view.bounds
let maskLayer = CAShapeLayer()
maskLayer.opacity = 1.0
maskLayer.lineWidth = 20.0
maskLayer.strokeColor = UIColor.black.cgColor
maskLayer.fillColor = UIColor.clear.cgColor
maskLayer.frame = view.bounds
testLayer.mask = maskLayer
view.layer.addSublayer(testLayer)
}
}
我在想,如果我創建一個遮罩層,那么我可以在遮罩層上繪制一條路徑並擦除部分圖像,例如:
let path = UIBezierPath()
path.move(to: CGPoint(x: 100.0, y: 100.0))
path.addLine(to: CGPoint(x: 200.0, y: 200.0))
maskLayer.path = path.cgPath
但是,當我添加maskLayer
時,它似乎覆蓋了整個圖像,我看不到它下面的內容。 我在這里做錯了什么?
您可以使用貝塞爾路徑掩碼通過創建自定義CALayer
子類並覆蓋draw(in ctx: CGContext)
來“擦除”路徑:
class MyCustomLayer: CALayer {
var myPath: CGPath?
var lineWidth: CGFloat = 24.0
override func draw(in ctx: CGContext) {
// fill entire layer with solid color
ctx.setFillColor(UIColor.gray.cgColor)
ctx.fill(self.bounds);
// we want to "clear" the stroke
ctx.setStrokeColor(UIColor.clear.cgColor);
// any color will work, as the mask uses the alpha value
ctx.setFillColor(UIColor.white.cgColor)
ctx.setLineWidth(self.lineWidth)
ctx.setLineCap(.round)
ctx.setLineJoin(.round)
if let pth = self.myPath {
ctx.addPath(pth)
}
ctx.setBlendMode(.sourceIn)
ctx.drawPath(using: .fillStroke)
}
}
這是一個完整的例子......我們將創建一個UIView
子類,因為效果將是“刮掉圖像”,我們將其命名為ScratchOffImageView
:
class ScratchOffImageView: UIView {
public var image: UIImage? {
didSet {
self.scratchOffImageLayer.contents = image?.cgImage
}
}
// adjust drawing-line-width as desired
// or set from
public var lineWidth: CGFloat = 24.0 {
didSet {
maskLayer.lineWidth = lineWidth
}
}
private class MyCustomLayer: CALayer {
var myPath: CGPath?
var lineWidth: CGFloat = 24.0
override func draw(in ctx: CGContext) {
// fill entire layer with solid color
ctx.setFillColor(UIColor.gray.cgColor)
ctx.fill(self.bounds);
// we want to "clear" the stroke
ctx.setStrokeColor(UIColor.clear.cgColor);
// any color will work, as the mask uses the alpha value
ctx.setFillColor(UIColor.white.cgColor)
ctx.setLineWidth(self.lineWidth)
ctx.setLineCap(.round)
ctx.setLineJoin(.round)
if let pth = self.myPath {
ctx.addPath(pth)
}
ctx.setBlendMode(.sourceIn)
ctx.drawPath(using: .fillStroke)
}
}
private let maskPath: UIBezierPath = UIBezierPath()
private let maskLayer: MyCustomLayer = MyCustomLayer()
private let scratchOffImageLayer: CALayer = CALayer()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() {
// Important, otherwise you will get a black rectangle
maskLayer.isOpaque = false
// add the image layer
layer.addSublayer(scratchOffImageLayer)
// assign the layer mask
scratchOffImageLayer.mask = maskLayer
}
override func layoutSubviews() {
super.layoutSubviews()
// set frames for mask and image layers
maskLayer.frame = bounds
scratchOffImageLayer.frame = bounds
// triggers drawInContext
maskLayer.setNeedsDisplay()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let currentPoint = touch.location(in: self)
maskPath.move(to: currentPoint)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let currentPoint = touch.location(in: self)
// add line to our maskPath
maskPath.addLine(to: currentPoint)
// update the mask layer path
maskLayer.myPath = maskPath.cgPath
// triggers drawInContext
maskLayer.setNeedsDisplay()
}
}
並且,一個示例視圖控制器:
class ScratchOffViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
guard let img = UIImage(named: "test.jpg") else {
fatalError("Could not load image!!!!")
}
let scratchOffView = ScratchOffImageView()
// set the "scratch-off" image
scratchOffView.image = img
// default line width is 24.0
// we can set it to a different width here
//scratchOffView.lineWidth = 12
// let's add a light-gray label with red text
// we'll overlay the scratch-off-view on top of the label
// so we can see the text "through" the image
let backgroundLabel = UILabel()
backgroundLabel.font = .italicSystemFont(ofSize: 36)
backgroundLabel.text = "This is some text in a label so we can see that the path is clear -- so it appears as if the image is being \"scratched off\""
backgroundLabel.numberOfLines = 0
backgroundLabel.textColor = .red
backgroundLabel.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
[backgroundLabel, scratchOffView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
backgroundLabel.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.7),
backgroundLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
backgroundLabel.centerYAnchor.constraint(equalTo: g.centerYAnchor),
scratchOffView.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.8),
scratchOffView.heightAnchor.constraint(equalTo: scratchOffView.widthAnchor, multiplier: 2.0 / 3.0),
scratchOffView.centerXAnchor.constraint(equalTo: backgroundLabel.centerXAnchor),
scratchOffView.centerYAnchor.constraint(equalTo: backgroundLabel.centerYAnchor),
])
}
}
開始時看起來像這樣 - 我使用了 3:2 的圖像,並將其覆蓋在帶有紅色文本的淺灰色標簽上,因此我們可以看到我們正在“刮掉”圖像:
然后,經過一點“抓撓”:
經過大量的“抓撓”:
maskLayer沒有初始路徑,因此它的內容沒有被填充,用 .clear 填充它也會完全掩蓋圖像。
這對我有用:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let image = UIImage(named: "test.jpg")!
let testLayer = CAShapeLayer()
testLayer.contents = image.cgImage
testLayer.frame = view.bounds
let maskLayer = CAShapeLayer()
maskLayer.strokeColor = UIColor.black.cgColor
maskLayer.fillColor = UIColor.black.cgColor
maskLayer.path = UIBezierPath(rect: view.bounds).cgPath
testLayer.mask = maskLayer
view.layer.addSublayer(testLayer)
/* optional for testing
let path = UIBezierPath()
path.move(to: CGPoint(x: 100.0, y: 100.0))
path.addLine(to: CGPoint(x: 200.0, y: 200.0))
maskLayer.path = path.cgPath
*/
}
實際上不確定描邊和填充的組合是否有效,但也許其他人想出了一個解決方案。 如果你想從你的路徑中剪下形狀,你可以嘗試這樣的事情:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let image = UIImage(named: "test.jpg")!
let testLayer = CAShapeLayer()
testLayer.contents = image.cgImage
testLayer.frame = view.bounds
let maskLayer = CAShapeLayer()
maskLayer.fillColor = UIColor.black.cgColor
maskLayer.fillRule = .evenOdd
testLayer.mask = maskLayer
view.layer.addSublayer(testLayer)
let path = UIBezierPath(rect: CGRect(x: 100, y: 100, width: 200, height: 20))
let fillPath = UIBezierPath(rect: view.bounds)
fillPath.append(path)
maskLayer.path = fillPath.cgPath
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.