2

I have a simple view that uses UITouch events to draw a UIBezierPath. It all works fine until I try to clear the view. Specifically, it works twice, and on the third attempt to draw a line, the app crashes.

For an extra bit of complexity, the UIViewController is wrapped in a UIViewControllerRepresentable for use in a SwiftUI project, although my gut instinct is that this isn't the cause of the problem.

Here's the code, and I've isolated the one line that's causing the crash, I just don't know why it's causing the crash.

import UIKit

class Canvas: UIView {
    
    var lineColor: UIColor!
    var lineWidth: CGFloat!
    
    var path: UIBezierPath!
    var touchPoint: CGPoint!
    var startingPoint: CGPoint!
    
    override func layoutSubviews() {
        self.clipsToBounds = true;
        self.isMultipleTouchEnabled = false;
        
        lineColor = .white
        lineWidth = 10
        
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        startingPoint = touch.location(in: self)
        if(path != nil && path.isEmpty == false) {
            clearCanvas()
        }
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touch = touches.first else { return }
        touchPoint = touch.location(in: self)
        path = UIBezierPath()
        path.move(to: startingPoint)
        path.addLine(to: touchPoint)
        startingPoint = touchPoint
        drawShapeLayer()
    }
    
    func drawShapeLayer() {
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = path.cgPath
        shapeLayer.strokeColor = lineColor.cgColor
        shapeLayer.lineWidth = lineWidth
        shapeLayer.fillColor = UIColor.clear.cgColor
        self.layer.addSublayer(shapeLayer)
        self.setNeedsDisplay()
    }
    
    func clearCanvas() {
        path?.removeAllPoints()
        // The next line causes the crash
        self.layer.sublayers?.forEach{ $0.removeFromSuperlayer() }
    }
}

I'm perpetually a Swift noob. Coming from Javascript, I don't have a great understanding of references and pointers and what not. My suspicion is that something related to the layers is disappearing and then the app is trying to access it, but I can't pin point where or why that's happening. I assumed the views layer and layer.subLayers property would always be there.

As always, any help is very much appreciated.

EDIT:

With Zombies enabled in Diagnostics, I get this error

DowntownControls[7663:5124409] *** -[CALayer convertPoint:fromLayer:]: message sent to deallocated instance 0x1070790f0
gargantuan
  • 8,888
  • 16
  • 67
  • 108
  • what happens if you take a copy of the sublayers array before forEaching over it with mutation: say 'let sublayers = self.layers.sublayers; sublayers.forEach ...' ? – Shadowrun May 19 '21 at 09:51
  • @Shadowrun - No difference unfortunately. However, I turned on Zombies and I get a more useful error message now. I'll update the question – gargantuan May 19 '21 at 10:04

0 Answers0