UIView draw always draws the whole frame

I am trying to build a drawing app for pencil with features like layers, importing photos, and undo redo, and initial tests of the responsiveness of the drawing engine are not so great.

I'm experimenting based on the SpeedSketch example, except using a bitmap CGContext as a back-buffer to store a layer's pixels. I figure I have to use rasters because of the importing images feature. For the same reason I don't think I can use PencilKit.

I've done some debugging and it seems that maybe the sluggishness is because every time draw is called in the drawing view, the whole frame is updated. I've overridden the setNeedsDisplay functions to try and see which rects are invalidated and this part seems fine. For whatever reason UIKit is always calling draw with a full frame.

here is the code in my view:

    override func setNeedsDisplay(_ rect : CGRect) {
        print("Needs display \(rect)")
        super.setNeedsDisplay(rect)
    }

    override func setNeedsDisplay() {
        print("Called set needs display for whole frame")
        super.setNeedsDisplay()
    }

    override func draw(_ rect: CGRect) {
        print("Draw rectangle \(rect) in bounds \(bounds.size)")
        
        guard let context = UIGraphicsGetCurrentContext() else {
            print("Failed to get context")
            return
        }

        if bitmapGraphicsContext != nil {
            if (cachedImage == nil ) {
                cachedImage = bitmapGraphicsContext!.makeImage()
            }
            let transformedRect = transformRect(rect)
            let image = cachedImage?.cropping(to: transformedRect)
            if let imageRef = image {
                context.draw(imageRef, in: rect)
            }
        } 
        context.setBlendMode(CGBlendMode.clear)

        if let stroke = strokeToDraw {
            draw(stroke: stroke, in: rect)
        }
    }

the output is: Called set needs display for whole frame

Draw rectangle (0.0, -0.17777, 557.5, 248.0) in bounds (557.6, 247.822)

Needs display (177.8, 142.91, 40.0, 40.0)

Draw rectangle (0.0, -0.17777, 557.5, 248.0) in bounds (557.6, 247.822) Would love some help if anyone knows what to do

So I set up the most minimal project I could exhibiting the behaviour and it still happens.

For some reason it took me a whole day to realise a workaround was to just keep track of the draw rectangle myself, overriding the setNeedsDisplay functions to store it in my own variable. Works lightning fast now :)

var actualDrawRect : CGRect?

override func setNeedsDisplay(_ rect : CGRect) {
    super.setNeedsDisplay(rect)
    if actualDrawRect == nil {
        actualDrawRect = rect
    } else {
        actualDrawRect = actualDrawRect!.union(rect)
    }
}
    
override func setNeedsDisplay() { 
    super.setNeedsDisplay()
    actualDrawRect = CGRect(origin: .zero, size: bounds.size)
}

I have the same problem.

I'm not sure it is a solution to keep track of the rectangles yourself because the image might need to be drawn for other reasons? If it's like on Windows, a part of a view might need to be updated because it becomes visible.

Also the draw operation is scheduled and might be called after multiple call to setNeedsDisplay.

UIView draw always draws the whole frame
 
 
Q