swiftswiftuisliderinstanceinit

Why do Sliders call the onEditingChanged closures of all other Sliders?


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) }

Solution

  • 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.

    .