SwiftUI 选择器在 ObservedObject 公共变量更新期间滚动时跳转

Posted

技术标签:

【中文标题】SwiftUI 选择器在 ObservedObject 公共变量更新期间滚动时跳转【英文标题】:SwiftUI picker jumps while scrolling during ObservedObject public var update 【发布时间】:2020-06-10 19:30:19 【问题描述】:

目前正在创建一个 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[$0]))")
                            
                        

...

TimerManager 管理一个计时器。 班级:

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 访问计时器类的 secondsLeft 变量来显示剩余时间。当计时器运行时,secondsLeft 变量会每秒更新一次,并且 ContentView 会重新呈现。 问题是,当计时器运行时,我不能“轻弹”我的选择器,它总是重置,这里也指出:https://forums.developer.apple.com/thread/127218。但与那篇文章不同的是,选择器的“选择”变量对问题没有任何影响。如果我从 secondsLeft 变量中删除 @Public,一切正常(问题是我无法显示剩余时间)。

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

感谢您的回答!

【问题讨论】:

也许环境对象会有所帮助?我们需要更多上下文,如果您可以创建一个可重现的示例,这将很有帮助:***.com/help/minimal-reproducible-example 谢谢!我刚刚添加了一些代码。 太好了,我能够使用您的代码编写一个简单的演示;但是,当我轻弹选择器时,它不会重置计时器,而是计时器标签“冻结”,直到我松开选择器。这是你的问题吗?还是我错过了什么? 只是为了确定:“计时器标签”是什么意思?选择器? 不,我创建了一个 Text("Time remaining: \(self.timerManager.secondsLeft)") 来显示剩余时间,它会倒计时。 【参考方案1】:

这个问题可以通过自定义 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/

希望这可以帮助遇到同样问题的每个人!

【讨论】:

以上是关于SwiftUI 选择器在 ObservedObject 公共变量更新期间滚动时跳转的主要内容,如果未能解决你的问题,请参考以下文章

在 SwiftUI 中使用分段式选择器在两个页面之间滑动

基本的 SwiftUI 选择器在屏幕变化时发出严重警告和奇怪的动作

无法使用 SwiftUI 使选取器居中

SwiftUI Segmented Picker 在点击时不会切换

SwiftUI Picker 多个错误,核心数据,布局

为啥 jQuery 选择器在这里不起作用?