Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI: How to get continuous updates from Slider

Tags:

ios

swift

swiftui

I'm experimenting with SwiftUI and the Slider control like this:

struct MyView: View {

    @State private var value = 0.5

    var body: some View {
        Slider(value: $value) { pressed in
        }
    }
}

I'm trying to get continuous updates from the Slider as the user drags it, however it appears that it only updates the value at the end of the value change.

Anyone played with this? know how to get a SwiftUI Slider to issue a stream of value changes? Combine perhaps?

like image 624
drekka Avatar asked Jun 23 '19 15:06

drekka


2 Answers

In SwiftUI, you can bind UI elements such as slider to properties in your data model and implement your business logic there.

For example, to get continuous slider updates:

import SwiftUI
import Combine

final class SliderData: BindableObject {

  let didChange = PassthroughSubject<SliderData,Never>()

  var sliderValue: Float = 0 {
    willSet {
      print(newValue)
      didChange.send(self)
    }
  }
}

struct ContentView : View {

  @EnvironmentObject var sliderData: SliderData

  var body: some View {
    Slider(value: $sliderData.sliderValue)
  }
}

Note that to have your scene use the data model object, you need to update your window.rootViewController to something like below inside SceneDelegate class, otherwise the app crashes.

window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(SliderData()))

enter image description here

like image 180
M Reza Avatar answered Oct 07 '22 16:10

M Reza


After much playing around I ended up with the following code. It's a little cut down to keep the answer short, but here goes. There was a couple of things I needed:

  • To read value changes from the slider and round them to the nearest integer before setting an external binding.
  • To set a localized hint value based on the integer.
struct AspectSlider: View {

    // The first part of the hint text localization key.
    private let hintKey: String

    // An external integer binding to be set when the rounded value of the slider
changes to a different integer.
    private let value: Binding<Int>

    // A local binding that is used to track the value of the slider.
    @State var sliderValue: Double = 0.0

    init(value: Binding<Int>, hintKey: String) {
        self.value = value
        self.hintKey = hintKey
    }

    var body: some View {
        VStack(alignment: .trailing) {

            // The localized text hint built from the hint key and the rounded slider value.
            Text(LocalizedStringKey("\(hintKey).\(self.value.value)"))

            HStack {
                Text(LocalizedStringKey(self.hintKey))
                Slider(value: Binding<Double>(
                    getValue: { self.$sliderValue.value },
                    setValue: { self.sliderChanged(toValue: $0) }
                    ),
                    through: 4.0) { if !$0 { self.slideEnded() } }
            }
        }
    }

    private func slideEnded() {
        print("Moving slider to nearest whole value")
        self.sliderValue = self.sliderValue.rounded()
    }

    private func sliderChanged(toValue value: Double) {
        $sliderValue.value = value
        let roundedValue = Int(value.rounded())
        if roundedValue == self.value.value {
            return
        }

        print("Updating value")
        self.value.value = roundedValue
    }
}
like image 26
drekka Avatar answered Oct 07 '22 16:10

drekka