Skip to content

Commit d7290d1

Browse files
committed
Use CA for mono progress bar due to high CPU usage with SwiftUI
1 parent 4d9c455 commit d7290d1

File tree

1 file changed

+59
-7
lines changed

1 file changed

+59
-7
lines changed

VirtualUI/Source/Installer/Steps/Restore Image Selection/Components/VirtualBuddyMonoProgressView.swift

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,9 @@ private struct RamRodProgressView: View {
5353

5454
var body: some View {
5555
ZStack {
56-
Rectangle()
57-
.fill(Color(white: 0.16))
58-
GeometryReader { proxy in
59-
Color(white: 0.82)
60-
.frame(width: proxy.size.width * (progress / 1.0), alignment: .leading)
61-
.animation(.linear, value: progress)
62-
}
56+
Rectangle().fill(Color(white: 0.16))
57+
58+
ProgressBarShapeView(progress: progress)
6359
}
6460
.clipShape(shape)
6561
.overlay(shape.stroke(Color(white: 0.25), lineWidth: 1))
@@ -71,6 +67,62 @@ private struct RamRodProgressView: View {
7167
}
7268
}
7369

70+
/**
71+
You see, animating a white rectangle growing in width is a very expensive operation that SwiftUI
72+
is completely unable to do by itself without consuming an unhealthy amount of CPU,
73+
so this uses Core Animation instead to offload that expensive computation to the WindowServer/GPU.
74+
*/
75+
private struct ProgressBarShapeView: NSViewRepresentable {
76+
typealias NSViewType = _Representable
77+
78+
var progress: Double = 0
79+
80+
func makeNSView(context: Context) -> _Representable {
81+
_Representable(frame: .zero)
82+
}
83+
84+
func updateNSView(_ nsView: _Representable, context: Context) {
85+
nsView.progress = progress
86+
}
87+
88+
final class _Representable: NSView {
89+
private lazy var bar = CALayer()
90+
91+
@Invalidating(.layout)
92+
var progress: Double = 0
93+
94+
override init(frame frameRect: NSRect) {
95+
super.init(frame: frameRect)
96+
97+
platformLayer.addSublayer(bar)
98+
bar.backgroundColor = .white
99+
bar.anchorPoint = CGPoint(x: 0, y: 0.5)
100+
}
101+
102+
required init?(coder: NSCoder) {
103+
fatalError()
104+
}
105+
106+
override func layout() {
107+
super.layout()
108+
109+
CATransaction.begin()
110+
CATransaction.setDisableActions(true)
111+
112+
bar.position = CGPoint(x: bounds.minX, y: bounds.midY)
113+
bar.frame.size.height = bounds.height
114+
115+
CATransaction.commit()
116+
117+
CATransaction.begin()
118+
CATransaction.setAnimationDuration(0.2)
119+
CATransaction.setAnimationTimingFunction(.init(name: .linear))
120+
bar.frame.size.width = bounds.width * progress
121+
CATransaction.commit()
122+
}
123+
}
124+
}
125+
74126
#if DEBUG
75127
#Preview {
76128
VMInstallationWizard.preview(step: .download)

0 commit comments

Comments
 (0)