SwiftUI 选择器在 ObservedObject public var update 期间滚动时跳转

SwiftUI picker jumps while scrolling during ObservedObject public var update

目前正在创建一个 SwiftUI 应用程序,我在尝试解决 ObservedObject 和选择器的问题时遇到了困难。 我的内容视图:

struct ContentView: View {
    @ObservedObject var timerManager = TimerManager()
...
var body: some View{
                 Circle()
                    .onTapGesture(perform: {  
                                self.timerManager.setTimerLength(seconds: self.settings.selectedSecondPickerIndex, minutes: self.settings.selectedMinutePickerIndex)
                                self.timerManager.start()
                    })

 Picker(selection: self.$settings.selectedSecondPickerIndex, label: Text("")) {
                            ForEach(0 ..< 60) {
                                Text("\(secondsToString(seconds: self.availableTimeInterval[[=11=]]))")
                            }
                        }
}
...
}

TimerManager 管理一个定时器。 class:

class TimerManager: ObservableObject {
    @Published var secondsLeft : Int = -1
    var secondsTotal : Int = -1
    var timer = Timer()

func start() {
        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { timer in
        if secondsLeft == 0 {
          self.secondsLeft = secondsTotal
          timer.invalidate()
        }
        else{
          self.secondsLeft -= 1
        }
       }
      RunLoop.current.add(timer, forMode: RunLoop.Mode.common)
}
  func setTimerLength(seconds: Int, minutes: Int) {
        let totalSeconds: Int = seconds + minutes * 60
        secondsTotal = totalSeconds
        secondsLeft = totalSeconds
    }
...
}

我需要从 ContentView 访问计时器 class 的 secondsLeft 变量以显示剩余时间。当计时器运行时,secondsLeft 变量每秒更新一次,并且 ContentView 被重新渲染。 问题是,虽然计时器是 运行,但我不能 "flick" 我的选择器,它总是重置,也在这里指出:https://forums.developer.apple.com/thread/127218。但与 post 不同的是,选择器的 "selection" 变量对问题没有任何影响。如果我从 secondsLeft 变量中删除 @Public,一切正常(问题是我无法显示剩余时间)。

有人知道如何解决这个问题吗?

感谢您的回答!

可以使用自定义 UIPickerView 解决此问题:

import SwiftUI

struct ContentView: View {

    @State var selection = 0

    var body: some View {

        VStack {

            FixedPicker(selection: $selection, rowCount: 10) { row in
                "\(row)"
            }
        }
    }
}

struct FixedPicker: UIViewRepresentable {
    class Coordinator : NSObject, UIPickerViewDataSource, UIPickerViewDelegate {
        @Binding var selection: Int
        
        var initialSelection: Int?
        var titleForRow: (Int) -> String
        var rowCount: Int

        func numberOfComponents(in pickerView: UIPickerView) -> Int {
            1
        }
        
        func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
            rowCount
        }
        
        func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
            titleForRow(row)
        }
        
        func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
            self.selection = row
        }
        
        init(selection: Binding<Int>, titleForRow: @escaping (Int) -> String, rowCount: Int) {
            self.titleForRow = titleForRow
            self._selection = selection
            self.rowCount = rowCount
        }
    }
    
    @Binding var selection: Int
    
    var rowCount: Int
    let titleForRow: (Int) -> String

    func makeCoordinator() -> FixedPicker.Coordinator {
        return Coordinator(selection: $selection, titleForRow: titleForRow, rowCount: rowCount)
    }

    func makeUIView(context: UIViewRepresentableContext<FixedPicker>) -> UIPickerView {
        let view = UIPickerView()
        view.delegate = context.coordinator
        view.dataSource = context.coordinator
        return view
    }
    
    func updateUIView(_ uiView: UIPickerView, context: UIViewRepresentableContext<FixedPicker>) {
        
        context.coordinator.titleForRow = self.titleForRow
        context.coordinator.rowCount = rowCount

        //only update selection if it has been changed
        if context.coordinator.initialSelection != selection {
            uiView.selectRow(selection, inComponent: 0, animated: true)
            context.coordinator.initialSelection = selection
        }
    }
}

我在这里找到了这个解决方案: https://mbishop.name/2019/11/odd-behaviors-in-the-swiftui-picker-view/

希望对遇到同样问题的大家有所帮助!