@@ -8,45 +8,35 @@ struct CircularProgressView: View {
8
8
var primaryColor : Color = . secondary
9
9
var backgroundColor : Color = . secondary. opacity ( 0.3 )
10
10
11
- @State private var rotation = 0.0
12
- @State private var trimAmount : CGFloat = 0.15
13
-
14
11
var autoCompleteThreshold : Float ?
15
12
var autoCompleteDuration : TimeInterval ?
16
13
17
14
var body : some View {
18
15
ZStack {
19
- // Background circle
20
- Circle ( )
21
- . stroke ( backgroundColor, style: StrokeStyle ( lineWidth: strokeWidth, lineCap: . round) )
22
- . frame ( width: diameter, height: diameter)
23
- Group {
24
- if let value {
25
- // Determinate gauge
16
+ if let value {
17
+ ZStack {
18
+ Circle ( )
19
+ . stroke ( backgroundColor, style: StrokeStyle ( lineWidth: strokeWidth, lineCap: . round) )
20
+
26
21
Circle ( )
27
22
. trim ( from: 0 , to: CGFloat ( displayValue ( for: value) ) )
28
23
. stroke ( primaryColor, style: StrokeStyle ( lineWidth: strokeWidth, lineCap: . round) )
29
- . frame ( width: diameter, height: diameter)
30
24
. rotationEffect ( . degrees( - 90 ) )
31
25
. animation ( autoCompleteAnimation ( for: value) , value: value)
32
- } else {
33
- // Indeterminate gauge
34
- Circle ( )
35
- . trim ( from: 0 , to: trimAmount)
36
- . stroke ( primaryColor, style: StrokeStyle ( lineWidth: strokeWidth, lineCap: . round) )
37
- . frame ( width: diameter, height: diameter)
38
- . rotationEffect ( . degrees( rotation) )
39
26
}
27
+ . frame ( width: diameter, height: diameter)
28
+
29
+ } else {
30
+ IndeterminateSpinnerView (
31
+ diameter: diameter,
32
+ strokeWidth: strokeWidth,
33
+ primaryColor: NSColor ( primaryColor) ,
34
+ backgroundColor: NSColor ( backgroundColor)
35
+ )
36
+ . frame ( width: diameter, height: diameter)
40
37
}
41
38
}
42
39
. frame ( width: diameter + strokeWidth * 2 , height: diameter + strokeWidth * 2 )
43
- . onAppear {
44
- if value == nil {
45
- withAnimation ( . linear( duration: 0.8 ) . repeatForever ( autoreverses: false ) ) {
46
- rotation = 360
47
- }
48
- }
49
- }
50
40
}
51
41
52
42
private func displayValue( for value: Float ) -> Float {
@@ -78,3 +68,55 @@ extension CircularProgressView {
78
68
return view
79
69
}
80
70
}
71
+
72
+ // We note a constant >10% CPU usage when using a SwiftUI rotation animation that
73
+ // repeats forever, while this implementation, using Core Animation, uses <1% CPU.
74
+ struct IndeterminateSpinnerView : NSViewRepresentable {
75
+ var diameter : CGFloat
76
+ var strokeWidth : CGFloat
77
+ var primaryColor : NSColor
78
+ var backgroundColor : NSColor
79
+
80
+ func makeNSView( context _: Context ) -> NSView {
81
+ let view = NSView ( frame: NSRect ( x: 0 , y: 0 , width: diameter, height: diameter) )
82
+ view. wantsLayer = true
83
+
84
+ guard let viewLayer = view. layer else { return view }
85
+
86
+ let fullPath = NSBezierPath (
87
+ ovalIn: NSRect ( x: 0 , y: 0 , width: diameter, height: diameter)
88
+ ) . cgPath
89
+
90
+ let backgroundLayer = CAShapeLayer ( )
91
+ backgroundLayer. path = fullPath
92
+ backgroundLayer. strokeColor = backgroundColor. cgColor
93
+ backgroundLayer. fillColor = NSColor . clear. cgColor
94
+ backgroundLayer. lineWidth = strokeWidth
95
+ viewLayer. addSublayer ( backgroundLayer)
96
+
97
+ let foregroundLayer = CAShapeLayer ( )
98
+
99
+ foregroundLayer. frame = viewLayer. bounds
100
+ foregroundLayer. path = fullPath
101
+ foregroundLayer. strokeColor = primaryColor. cgColor
102
+ foregroundLayer. fillColor = NSColor . clear. cgColor
103
+ foregroundLayer. lineWidth = strokeWidth
104
+ foregroundLayer. lineCap = . round
105
+ foregroundLayer. strokeStart = 0
106
+ foregroundLayer. strokeEnd = 0.15
107
+ viewLayer. addSublayer ( foregroundLayer)
108
+
109
+ let rotationAnimation = CABasicAnimation ( keyPath: " transform.rotation " )
110
+ rotationAnimation. fromValue = 0
111
+ rotationAnimation. toValue = 2 * Double. pi
112
+ rotationAnimation. duration = 1.0
113
+ rotationAnimation. repeatCount = . infinity
114
+ rotationAnimation. isRemovedOnCompletion = false
115
+
116
+ foregroundLayer. add ( rotationAnimation, forKey: " rotationAnimation " )
117
+
118
+ return view
119
+ }
120
+
121
+ func updateNSView( _: NSView , context _: Context ) { }
122
+ }
0 commit comments