簡體   English   中英

如何遮蓋和擦除 CAShapeLayer?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM