如何使CAShapeLayer遮罩在现有CAShapeLayer绘图上绘制清晰的线条?

时间:2019-09-08 22:16:41

标签: swift calayer uibezierpath cashapelayer eraser

我正在尝试使用图层蒙版创建一个橡皮擦。 使用口罩时,我得到以下结果: Eraser Fail 我的环境是这样设置的: 从底视图/层到顶...

  1. BackgroundImageView-我正在使用此图像视图来保存绘图表面的“背景”。它是可以更改的层,可以保存新的“模板”并调入其中。我将其分开,以使用户无法擦除绘图表面。并且背景由CAShapeLayers组成,这些图层代表不同的纸张类型。
  2. MainImageView-我正在使用此图像视图来执行用户启动的所有绘图。因此,我触摸并拖动手指,然后将新的CAShapeLayer添加到图像视图中。这使用户的绘图与“绘图表面”分开。这也是我要擦除的地方
  3. PageImagesView-我使用此视图保存用户可以添加到页面的图像,并对其进行移动/调整大小。我不希望橡皮擦影响图像,但是如果MainImageView中绘制的线条越过图像并且需要擦除,则应该让图像显示出来,而不是去除图像的一部分。

这是我当前的代码在绘制时要获得平滑线条的样子,但是会产生不良的橡皮擦效果...

import UIKit
import QuartzCore

class DrawPanZom: UIScrollView {
    var drawPath = UIBezierPath()
    var images = [UIImage?]()
    weak var newImage: UIImageView?
    var newImages: [UIImageView?] = []
    var brush: CGFloat = 10.0
    var color = UIColor.black
    var opacity: CGFloat = 1.0
    var swiped = false
    var eraser = false
    var canvasCleared = false
    var drawLine: Bool = false
    var drawFreeHand: Bool = false
    var twoPoints = [CGPoint]()
    var highlighter: Bool = false
    var pinchZoom: Bool = false
    weak var strokeColor: UIColor?
    var isFirst = false
    var lastPoint: CGPoint?
    var firstPoint = CGPoint.zero
    var secondPoint: CGPoint?
    var pageDrawingPath: String?
    private let forceSensitivity: CGFloat = 4.0
    private let defaultLineWidth: CGFloat = 6
    //private var _image1: UIImage?
    var lineArray: [CGPoint] = []
    let loadPage = LoadPage()
    private var drawingLayer: CAShapeLayer?
    var midPoint: CGPoint?
    var canUndo = false
    var canRedo = false

    @IBOutlet weak var pageImagesView: UIView!
    @IBOutlet weak var backgroundImageView: UIImageView!
    @IBOutlet weak var mainImageView: UIImageView!
    @IBOutlet weak var eraserImageView: UIImageView!
    @IBOutlet weak var drawingView: UIView!
    var completionHandler:((String) -> Bool)?
    var undoHandler:(([CALayer]) -> Bool)?
    var canUndoHandler:((Bool) -> Bool)?
    var imageMovedHandler:((_ image: UIImageView, _ index: Int) -> Bool)?
    var line = [CGPoint]() //{
//        didSet { checkIfTooManyPoints() }
//    }
    //var newLayer: CALayer = CALayer()
    var subLayers: [CALayer] {
        return mainImageView.layer.sublayers ?? [CALayer]()
    }

    override func draw(_ layer: CALayer, in ctx: CGContext) {
        if eraser {
            //var rect: CGRect = CGRect()
            let linePath = UIBezierPath(rect: mainImageView.bounds)

            for (index, point) in line.enumerated() {
                if index == 0 {
                    midPoint = CGPoint(
                        x: (point.x + point.x) / 2,
                        y: (point.y + point.y) / 2
                    )
                    //rect = CGRect(x: midPoint!.x, y: midPoint!.y, width: brush, height: brush)
                    linePath.move(to: midPoint!)
                } else {
                    midPoint = CGPoint(
                        x: (point.x + line[index - 1].x) / 2,
                        y: (point.y + line[index - 1].y) / 2
                    )
                    //rect = CGRect(x: midPoint!.x, y: midPoint!.y, width: brush, height: brush)
                    linePath.addQuadCurve(to: midPoint!, controlPoint: line[index - 1])
                }
            }

            let maskLayer = CAShapeLayer()
            maskLayer.lineWidth = brush
            maskLayer.lineCap = .round
            maskLayer.strokeColor = UIColor.clear.cgColor
            maskLayer.fillColor = UIColor.black.cgColor
            maskLayer.opacity = 1.0
            maskLayer.path = linePath.cgPath
            maskLayer.fillRule = .evenOdd
            mainImageView.layer.addSublayer(maskLayer)
            mainImageView.layer.mask = maskLayer

        } else {
            let drawingLayer = self.drawingLayer ?? CAShapeLayer()
            let linePath = UIBezierPath()
            drawingLayer.contentsScale = UIScreen.main.scale
            drawingLayer.zPosition = 4
            if drawLine {
                var newLine = CGPoint()
                if line.count > 0 {
                    let xVariance = (line[0].x - line.last!.x)
                    let yVariance = (line.last!.y - line[0].y)
                    print("xVariance: \(xVariance), yVariance: \(yVariance)")

                    if (line[0].x - line.last!.x) <= 10, (line[0].x - line.last!.x) >= -10 {
                        newLine = CGPoint(x: line[0].x, y: line.last!.y)
                    } else {
                        newLine = line.last!
                    }
                    if (line.last!.y - line[0].y) <= 10, (line.last!.y - line[0].y ) >= -10 {
                        newLine = CGPoint(x: line.last!.x, y: line[0].y)
                    } else {
                        newLine = line.last!
                    }
                    linePath.move(to: line[0])
                    linePath.addLine(to: newLine)
                }

            } else if drawFreeHand || eraser {
                for (index, point) in line.enumerated() {
                    if index == 0 {
                        midPoint = CGPoint(
                            x: (point.x + point.x) / 2,
                            y: (point.y + point.y) / 2
                        )
                        linePath.move(to: midPoint!)
                    } else {
                        midPoint = CGPoint(
                            x: (point.x + line[index - 1].x) / 2,
                            y: (point.y + line[index - 1].y) / 2
                        )
                        linePath.addQuadCurve(to: midPoint!, controlPoint: line[index - 1])
                    }
                }
            }
            linePath.stroke(with: .clear, alpha: 1.0)
            drawingLayer.path = linePath.cgPath
            drawingLayer.opacity = Float(opacity)
            drawingLayer.lineWidth = brush
            drawingLayer.lineCap = .round
            drawingLayer.lineJoin = .round
            drawingLayer.strokeColor = color.cgColor
            drawingLayer.fillColor = UIColor.clear.cgColor
            drawingLayer.isOpaque = false

            if self.drawingLayer == nil {
                self.drawingLayer = drawingLayer
                mainImageView.layer.addSublayer(drawingLayer)
            }
        }
    }

    func checkIfTooManyPoints() {
        var maxPoints = 0

        if drawLine {
            maxPoints = 500
        } else {
            maxPoints = 21
        }

        if line.count > maxPoints {
            updateFlattenLayer()
            _ = line.removeFirst(maxPoints - 2)
        }
    }

    func flattenImage() {
        updateFlattenLayer()
        line.removeAll()
    }

    func updateFlattenLayer() {
        let layer = subLayers
        if layer.count > 0 {
            _ = canUndoHandler?(true)
        } else {
            _ = canUndoHandler?(false)
        }
        guard let drawingLayer = drawingLayer, let optionalDrawing = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(NSKeyedArchiver.archivedData(withRootObject: drawingLayer, requiringSecureCoding: false)) as? CAShapeLayer?, let newDrawing = optionalDrawing else { return }
        mainImageView.layer.addSublayer(newDrawing)
        _ = undoHandler?(layer)
    }

    func removeDrawingLayer(){
        drawingLayer?.removeFromSuperlayer()
        line.removeAll()
        layer.setNeedsDisplay()
    }

    func clear() {
        emptyFlattenLayers()
        drawingLayer?.removeFromSuperlayer()
        drawingLayer = nil
        line.removeAll()
        layer.setNeedsDisplay()
    }

    func emptyFlattenLayers() {
        for case let layer as CAShapeLayer in subLayers {
            layer.removeFromSuperlayer()
        }
    }

    func calculateRectBetween(lastPoint: CGPoint, newPoint: CGPoint) -> CGRect {
        let originX = min(lastPoint.x, newPoint.x) - (brush / 2)
        let originY = min(lastPoint.y, newPoint.y) - (brush / 2)

        let maxX = max(lastPoint.x, newPoint.x) + (brush / 2)
        let maxY = max(lastPoint.y, newPoint.y) + (brush / 2)

        let width = maxX - originX
        let height = maxY - originY

        return CGRect(x: originX, y: originY, width: width, height: height)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        guard let touch = touches.first else { return }

        _ = completionHandler?("toggleMenus")

        if touches.count == 1 {
            line.removeAll()
            lineArray.removeAll()
            let location = touch.location(in: mainImageView)
            line.append(location)
            lineArray.append(location)
            firstPoint = location
            lastPoint = location
            swiped = false
            isFirst = true
        } else {
            line.removeAll()
            lineArray.removeAll()
        }
        //}

    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)
        guard let touch = touches.first else { return }

        let anyObjectTouch = event?.allTouches?.first
        var myMovableImage: UIImageView?
        if newImages.count > 0 {
            newImages.forEach { (image) in
                if anyObjectTouch?.view == image {
                    myMovableImage = image
                }
            }
        }

        if anyObjectTouch?.view == myMovableImage {
            if myMovableImage != nil {
                    if myMovableImage!.layer.borderWidth > 0 {
                        let location: CGPoint? = anyObjectTouch?.location(in: mainImageView)
                        myMovableImage!.center = location!
                    }
                }
        } else if touches.count == 1 {
            swiped = true
            let currentLocation = touch.location(in: mainImageView)
            line.append(currentLocation)
            if eraser {

            }
            lastPoint = line.last ?? .zero
            let rect = calculateRectBetween(lastPoint: lastPoint!, newPoint: currentLocation)
            layer.setNeedsDisplay(rect)

            if highlighter {
                opacity = 0.2
            } else {
                opacity = 1
            }
        } else {
            line.removeAll()
            lineArray.removeAll()
        }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        //let layers = subLayers
        guard let touch = touches.first else { return }

        let anyObjectTouch = event?.allTouches?.first
        var myMovableImage: UIImageView?

        if newImages.count > 0 {
            newImages.forEach { (image) in
                if anyObjectTouch?.view == image {
                    myMovableImage = image
                    //index = index + 1
                }
            }
        }

        if anyObjectTouch?.view == myMovableImage {
            if myMovableImage != nil {
                let imageIndex = newImages.firstIndex(of: myMovableImage)
                _ = imageMovedHandler?(myMovableImage!, imageIndex!)
            }
        }
        if touches.count == 1 {
            if swiped {
                if highlighter {
                    opacity = 0.5
                } else {
                    opacity = 1
                }
            }
            let currentLocation = touch.location(in: mainImageView)
            line.append(currentLocation)
            lineArray.append(currentLocation)

            flattenImage()
        } else {
            line.removeAll()
            lineArray.removeAll()
        }

    }
}
//drawing helper
extension DrawPanZom {

    func clearLayers() {
        let theseLayers = mainImageView.layer.sublayers
        if theseLayers!.count > 0 {
            backgroundImageView.layer.sublayers?.forEach {
                $0.removeFromSuperlayer()
            }
        }

    }

    func setFromDrawing(fromDrawing: [CALayer]) {
        mainImageView.layer.sublayers = fromDrawing
        setNeedsDisplay()
    }
}

没有任何“错误”。但是,它不能“干净地”擦除被触摸的区域。看起来这条线实际上是在渲染为某种形状。有人知道我在做什么错吗?

预期结果应为: enter image description here

0 个答案:

没有答案