Home > Enterprise >  CALayer drawing outside of view
CALayer drawing outside of view

Time:06-16

I have this behaviour with this code:

import UIKit

class Page: UIView {
    
    var bezierMemory = [BezierRecord]()
    var currentBezier: UIBezierPath = UIBezierPath()
    
    var firstPoint: CGPoint = CGPoint()
    var previousPoint: CGPoint = CGPoint()
    var morePreviousPoint: CGPoint = CGPoint()
    var previousCALayer: CALayer = CALayer()
    
    var pointCounter = 0
    
    var selectedPen: Pen = Pen(width: 3.0, strokeOpacity: 1, strokeColor: .red, fillColor: .init(gray: 0, alpha: 0.5), isPencil: true, connectsToStart: true, fillPencil: true)
    
    enum StandardPageSizes {
        case A4, LEGAL, LETTER
    }
    
    var firstCALayer = true
    var pointsTotal = 0
    
    override init(frame: CGRect) {
        super.init(frame: .zero)
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        guard let touch = touches.first else { return }
        let point = touch.location(in: self)
        firstPoint = point
        
        pointCounter = 1
        
        currentBezier = UIBezierPath()
        currentBezier.lineWidth = selectedPen.width
        selectedPen.getStroke().setStroke()
        currentBezier.move(to: point)
        
        previousPoint = point
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)
        guard let touch = touches.first else { return }
        let point = touch.location(in: self)
        pointCounter  = 1
        
        if (pointCounter == 3) {
            let midpoint = CGPoint(x: (morePreviousPoint.x   point.x)/2.0, y: (morePreviousPoint.y   point.y)/2.0)
            currentBezier.addQuadCurve(to: midpoint, controlPoint: morePreviousPoint)
            let updatedCALayer = CAShapeLayer()
            updatedCALayer.path = currentBezier.cgPath
            updatedCALayer.lineWidth = selectedPen.width
            updatedCALayer.opacity = selectedPen.strokeOpacity
            updatedCALayer.strokeColor = selectedPen.getStroke().cgColor
            updatedCALayer.fillColor = selectedPen.getFill()
            if (firstCALayer) {
                layer.addSublayer(updatedCALayer)
                firstCALayer = false
            } else {
                layer.replaceSublayer(previousCALayer, with: updatedCALayer)
            }
            previousCALayer = updatedCALayer
            pointCounter = 1
        }
        
        morePreviousPoint = previousPoint
        previousPoint = point
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        guard let touch = touches.first else { return }
        let point = touch.location(in: self)
        
        if (pointCounter != 3) {
            if (selectedPen.connectsToStart) {
                currentBezier.addQuadCurve(to: firstPoint, controlPoint: previousPoint)
            } else {
                currentBezier.addQuadCurve(to: point, controlPoint: previousPoint)
            }
            let updatedCALayer = CAShapeLayer()
            updatedCALayer.path = currentBezier.cgPath
            updatedCALayer.lineWidth = selectedPen.width
            updatedCALayer.opacity = selectedPen.strokeOpacity
            updatedCALayer.strokeColor = selectedPen.getStroke().cgColor
            updatedCALayer.fillColor = selectedPen.getFill()
            if (firstCALayer) {
                layer.addSublayer(updatedCALayer)
                firstCALayer = false
            } else {
                // layer.setNeedsDisplay()
                layer.replaceSublayer(previousCALayer, with: updatedCALayer)
            }
        }
        
        firstCALayer = true
        let bezierRecord = BezierRecord(bezier: currentBezier, strokeColor: selectedPen.getStroke(), fillColor: selectedPen.getFill(), strokeWidth: selectedPen.width)
        bezierMemory.append(bezierRecord)
    }
    
    private func normPoint(point: CGPoint) -> CGPoint {
        return CGPoint(x: point.x/frame.width, y: point.y/frame.height)
    }
    
    public class BezierRecord {
        var bezier: UIBezierPath
        var strokeColor: UIColor
        var strokeWidth: CGFloat
        var fillColor: CGColor
        
        init(bezier: UIBezierPath, strokeColor: UIColor, fillColor: CGColor, strokeWidth: CGFloat) {
            self.bezier = bezier
            self.strokeColor = strokeColor
            self.strokeWidth = strokeWidth
            self.fillColor = fillColor
        }
    }
    
}

Really the only relevant parts are touchesMoved and touchesEnded, where CALayer's are dealt with. As you can see from the gif, I can draw outside the bounds of the Page (UIView) as long as I start drawing inside the bounds. I do not want this - what I would like is something where you can maintain a stroke outside of the bounds of the Page (as long as you start it on the Page), but the stroke wont appear outside of the Page. Any ideas?

EDIT: I should add that for these UIBezierCurves, (0, 0) is considered to be the top left of the Page (white), and not the entire view. Thus, for example, beziers that start on the page and continue on, are negative.

CodePudding user response:

All you should need to do is set .clipsToBounds = true on the view.

One approach:

override init(frame: CGRect) {
    super.init(frame: .zero)
    commonInit()
}
required init?(coder: NSCoder) {
    super.init(coder: coder)
    commonInit()
}
func commonInit() {
    self.clipsToBounds = true
}

You could put self.clipsToBounds = true in both of the init funcs, but it is common practice (no pun intended) to add a "common init" func like this (it can be named whatever... this is how I do it). Frequently we have other "initial setup" code that we want called, and this avoids duplicating the code.

  • Related