I built a rockin' slider View component, but it's init() always gets called with every adjustment? And all other slider init()'s at the same time too? I am brand new to Swift programming... etc., but I'm not new to logic.
I modeled my slider like the Swift Slider source code that I can view in Xcode (more like declarations). I was totally shocked to find that the Swift Slider DOES THE EXACT SAME THING TOO?!? Which is really spooky to me, in that I usually do some heavy computation upon Slider changes, and I often have multiple Sliders in a View - in other words, this issue represents a lot of wasted CPU time as adjusting one triggers all the others to recompute too?!? And, if you ask me, they are all re-init()'ing too? Why would some other components closures be called? Must be some instancing bug?
Forget about my slider, here is the Swift Slider doing it. Change Slider 1 and Slider 2 recomputes too ?!? (just printing out here, but each onEditingChanged closure could be a half-a-page of code and/or other func calls):
import SwiftUI
struct ContentView: View {
@State var value1: CGFloat = 0.5
@State var value2: CGFloat = 0.25
var body: some View {
Spacer()
Slider(value: $value1) //{ let _ = print("Slider 1 updating") }
.padding()
Spacer()
Slider(value: $value2) { let _ = print("Slider 2 updating") }
.padding()
Spacer()
}
}
#Preview { ContentView().preferredColorScheme(.dark) }
After reviewing all the responses (thank you), the problem seems to be that, without adding the (Bool) in
provided by the .onEditingChanged
closure, the true .onEditingChanged
closure is not being called. The fix with the shortest code is to use:
Slider(value: $value2) { _ in print("Slider 2 updating") }
But then what was being called isn't entirely clear either. One of the Slider's declarations is:
nonisolated public init<V>(value: Binding<V>, in bounds: ClosedRange<V> = 0...1, @ViewBuilder label: () -> Label, @ViewBuilder minimumValueLabel: () -> ValueLabel, @ViewBuilder maximumValueLabel: () -> ValueLabel, onEditingChanged: @escaping (Bool) -> Void = { _ in }) where V : BinaryFloatingPoint, V.Stride : BinaryFloatingPoint
where there are several @ViewBuilder
parameters with closures as candidates - it could think it's redrawing: label
, minimumValueLabel
, or maximumValueLabel
(which all seem unnecessary to redraw too), but I figured that without my closure 'returning' a Label
or ValueLabel
, as required for those other parameter closures respectively, the compiler would eliminate those as candidates, as only the .onEditingChanged
closure returns a Void
, which is what my closure was returning. Now I'm not sure which it thinks my closure was? In any case, adding the Bool with " _ in" at the start of my closure distinguishes it most clearly as meant for the .onEditingChanged
parameter specifically.
I also learned from Swift docs that if any @State
property changes, if the compiler can't be sure of what all that might effect, then it redraws *everything*, sometimes twice, as if it's looking for what all changed.
In the end, not being sure of exactly how to properly implement Slider's .onEditingChanged
functionality in my own slider, I'm just going to use a .onChange(of: value2){}
modifier to ensure that multiple entire closures aren't being re-executed when only one slider is moved.
.