SwiftUI Performance - FPS drops when view is updated on every scroll action

I'm implementing a 'ruler' view similar to what you would find in Sketch / Photoshop / etc. Basically a ruler at the top which shows you the current view size and updates as you zoom in/out. In my example, it draws about ~250 rectangles.


I wanted to do it with SwiftUI but I'm getting into performance issues.
When I update the view's scale with a slider, FPS drops noticeably.
(Testing on macOS Catalina 10.15.7, Xcode 12.0.1, Macbook Air.)

I wonder if I hit the limits of SwiftUI and should switch to Metal, or am I missing some optimization here?
Note that I did add .drawingGroup() modifier but it doesn't seem to help in any way.

Here's a sample app to download: Github


Here's a code for the "ruler" view:

Code Block swift
struct Timeline: View {
let scale: Double
private let minLongTickWidth = 30.0
let LARGE_TICKS_COUNT = 50
var SMALL_TICKS_COUNT = 5
var body: some View {
HStack(alignment: .bottom, spacing: 0) {
ForEach(ticks, id: \.self) { time in
HStack(alignment: .bottom, spacing: 0) {
LongTick(text: "X")
.frame(width: smallTickWidth, alignment: .leading)
ForEach(1..<SMALL_TICKS_COUNT, id: \.self) { time in
SmallTick()
.frame(width: smallTickWidth, alignment: .leading)
}
}
.frame(width: longTickWidth, alignment: .leading)
}
}
.background(Color(NSColor.black))
.drawingGroup()
}
var oneLongTickDurationInMs: Double {
let pointsForOneMilisecond = scale / 1000
var msJump = 1
var oneLongTickDurationInMs = 1.0
while true {
let longTickIntervalWidth = oneLongTickDurationInMs * pointsForOneMilisecond
if longTickIntervalWidth >= minLongTickWidth {
break
}
oneLongTickDurationInMs += Double(msJump)
switch oneLongTickDurationInMs {
case 0..<10: msJump = 1
case 10..<100: msJump = 10
case 100..<1000: msJump = 100
case 1000..<10000: msJump = 1000
default: msJump = 10000
}
}
return oneLongTickDurationInMs
}
var longTickWidth: CGFloat {
CGFloat(oneLongTickDurationInMs / 1000 * scale)
}
var ticks: [Double] {
let oneLongTickDurationInMs = self.oneLongTickDurationInMs
let tickTimesInMs = (0...LARGE_TICKS_COUNT).map { Double($0) * oneLongTickDurationInMs }
return tickTimesInMs
}
var smallTickWidth: CGFloat {
longTickWidth / CGFloat(SMALL_TICKS_COUNT)
}
}
struct SmallTick: View {
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(width: 1)
.frame(maxHeight: 8)
}
}
struct LongTick: View {
let text: String
var body: some View {
Rectangle()
.fill(Color.red)
.frame(width: 1)
.frame(maxHeight: .infinity)
.overlay(
Text(text)
.font(.system(size: 12))
.fixedSize()
.offset(x: 3, y: 0)
,
alignment: .topLeading
)
}
}


LazyHStack might help if your view is inside a scroll view
SwiftUI Performance - FPS drops when view is updated on every scroll action
 
 
Q