If you perform your own custom view draw by overriding UIView
draw(_:)
in your subclass, and you trigger it by calling setNeedsDisplay(_:)
, it's a performance boost if you restrict drawing to just the dirty rectangle passed to draw(_:)
:
override func draw(_ rect: CGRect) {
//Only draw inside rect, and not the entire UIView specified by the `bounds` property
}
On macOS, the functions getRectsBeingDrawn(_:count:)
and needsToDraw(_:)
can be used to discover whether you want to draw, but they are not available on iOS.
I've found that a simple way the handle this is to set the dirty rect as the clipping region:
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
context?.saveGState()
let path = UIBezierPath(rect: rect)
path.addClip()
//Perform your drawing in `bounds` here
context?.restoreGState()
}
I'm recently building an app that involves panning many views that implement custom drawing and just introducing clipping makes the performance improve from being dog slow to buttery smooth.
And if you want to go one step further, you can use the "context" technique I mentioned in #168 to clean up the code:
Implement this:
func withGraphicsContext(block: (CGContext?) -> ()) {
let context = UIGraphicsGetCurrentContext()
context?.saveGState()
block(context)
context?.restoreGState()
}
And you can rewrite draw(_:)
:
override func draw(_ rect: CGRect) {
withGraphicsContext { context in
let path = UIBezierPath(rect: rect)
path.addClip()
//Perform your drawing in `bounds` here
}
}
There is a technote which mentions:
Each UIView is treated as a single element. When you request a redraw, in part or whole, by calling -setNeedsDisplayInRect: or -setNeedsDisplay:, the entire view will be marked for updates
I did some testing and it seems like the statement is either outdated or wrong. But even if it becomes true in the future, clipping wouldn't break your code. So happy drawing!
Your feedback is valuable: Do you want more nuggets like this? Yes or No
.
.