关于ios:SwiftUI:如何从Slider获取连续更新

SwiftUI: How to get continuous updates from Slider

我正在尝试像这样的SwiftUI和Slider控件:

1
2
3
4
5
6
7
8
9
struct MyView: View {

    @State private var value = 0.5

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

我试图在用户拖动它时从Slider获取连续更新,但是看来它仅在值更改结束时更新值。

有人玩过这个吗? 知道如何让SwiftUI滑块发布价值变化流? 结合起来?


在SwiftUI中,您可以将UI元素(例如滑块)绑定到数据模型中的属性,并在那里实现业务逻辑。

例如,要获得连续的滑块更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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)
  }
}

请注意,要使场景使用数据模型对象,您需要将window.rootViewController更新为SceneDelegate类中的以下内容,否则应用程序将崩溃。

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

enter image description here


经过大量的尝试,我最终得到了以下代码。为了使答案简短一点,它做了一些削减,但是这里有。我需要做几件事:

  • 要从滑块读取值更改并将其四舍五入为最接近的整数,然后再设置外部绑定。
  • 根据整数设置本地化的提示值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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
    }
}


在版本11.4.1(11E503a)和Swift 5中,我没有复制它。
通过使用Combine,我可以不断从滑块更改中进行更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class SliderData: ObservableObject {

  @Published var sliderValue: Double = 0
  ...

}

struct ContentView: View {

  @ObservedObject var slider = SliderData()

  var body: some View {
    VStack {
      Slider(value: $slider.sliderValue)
      Text(String(slider.sliderValue))
    }
  }
}


我无法在iOS 13 Beta 2上重现此问题。您要针对哪个操作系统?

使用自定义绑定,不仅可以在编辑结束之后,还可以为每个小的更改打印值。

1
Slider(value: Binding<Double>(getValue: {0}, setValue: {print($0)}))

请注意,闭包({ pressed in })仅在编辑结束开始和结束时报告,值流仅传递到绑定中。


只需使用Slider的onEditingChanged参数即可。当用户移动滑块或仍与滑块接触时,该参数为true。当参数从true更改为false时,我会进行更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct MyView: View {

    @State private var value = 0.5

    func update(changing: Bool) -> Void {
        // Do whatever
    }

    var body: some View {
        Slider(value: $value, onEditingChanged: {changing in self.update(changing) })
        { pressed in }
    }
}


iOS 13.4,Swift 5.x

一个基于Mohammid出色解决方案的答案,只是我不想使用环境变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class SliderData: ObservableObject {
let didChange = PassthroughSubject<SliderData,Never>()

@Published var sliderValue: Double = 0 {
  didSet {
    print("sliderValue \\(sliderValue)")
    didChange.send(self)
   }
  }
}

@ObservedObject var sliderData:SliderData

Slider(value: $sliderData.sliderValue, in: 0...Double(self.textColors.count))

只需对ContentView_Preview进行少量更改,然后在SceneDelegate中进行相同的更改。

1
2
3
4
5
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
      ContentView(sliderData: SliderData.init())
    }
}