【问题标题】:Using reduce to change struct in Swift在 Swift 中使用 reduce 改变结构
【发布时间】:2021-12-10 04:15:03
【问题描述】:

我试图了解如何使用 reduce 来更改数组中的结构(或类?)。创建 4 个倒数计时器,点击暂停当前计时器并开始下一个计时器。所以我尝试了这样的事情:

    var timer1 = CountdownTimer()
    // var timer2 = CountdownTimer() etc.
    .onTapGesture(perform: {
        var timers: [CountdownTimer] = [timer1, timer2, timer3, timer4]
        var last = timers.reduce (false) {
          (setActive: Bool, nextValue: CountdownTimer) -> Bool in
          if (nextValue.isActive) {
            nextValue.isActive = false;
            return true
          } else {
            return false
          }
        }
        if (last) {
          var timer = timers[0]
          timer.isActive = true
        }
    })


############# CountdownTimer is a struct ######

struct CountdownTimer {
  var timeRemaining: CGFloat = 1000
  var isActive: Bool = false
}

这不起作用,我看到了两个错误

  1. 数组中的计时器是计时器的副本,而不是实际计时器,因此更改它们实际上不会更改屏幕上显示的计时器。
  2. nextValue(即下一个计时器)无法更改,因为它是 reduce 声明中的 let 变量。我不知道如何更改它(或者它是否相关,因为它可能是计时器副本的副本,而不是我真正想要更改的那个)。

我是否以一种对 Swift 来说惯用错误的方式来处理这个问题?我应该如何更改原来的计时器?

【问题讨论】:

  • 在 SwiftUI 中将模型与视图正确分离很重要。程序代码属于您的模型。您的视图只是显示模型并调用模型中的方法以响应用户交互。您可能希望您的计时器对象是引用类型(类而不是结构),因为您希望它们是可变的。

标签: ios swift swiftui


【解决方案1】:

我同意 Paul 的观点,即这很可能都应该被提取到一个可观察的模型对象中。我会让该模型保存一个任意的计时器列表,以及当前活动计时器的索引。 (我会在最后给出一个例子。)

但仍然值得探索如何完成这项工作。

首先,SwiftUI 视图不像 UIKit 那样是屏幕上的实际视图。它们是屏幕上视图的描述。它们是数据。它们可以随时被复制和销毁。所以它们是只读对象。您跟踪其可写状态的方式是通过@State 属性(或类似的东西,如@Binding@StateObject@ObservedObject 等)。所以你的属性需要标记为@State

@State var timer1 = CountdownTimer()
@State var timer2 = CountdownTimer()
@State var timer3 = CountdownTimer()
@State var timer4 = CountdownTimer()

正如您所发现的,这种代码不起作用:

var timer = timer1
timer.isActive = true

这会生成timer1副本并修改副本。相反,您希望 WriteableKeyPath 访问属性本身。例如:

let timer = \Self.timer1  // Note capital Self
self[keyPath: timer].isActive = true

最后,reduce 是错误的工具。 reduce 的重点是将一个序列缩减为一个值。它不应该有像修改值这样的副作用。相反,您只想找到正确的元素,然后更改它们。

为此,如果能够轻松跟踪“这个元素和下一个元素,最后一个元素跟在第一个元素之后”,那就太好了。这看起来很复杂,但如果你包含Swift Algorithms,它会非常简单。这给出了cycled(),它返回一个永远重复其输入的序列。为什么有用?因为那样你就可以这样做了:

zip(timers, timers.cycled().dropFirst())

返回

(value1, value2)
(value2, value3)
(value3, value4)
(value4, value1)

完美。有了它,我可以获取第一个活动计时器(keypath)及其后继者,并更新它们:

let timers = [\Self.timer1, \.timer2, \.timer3, \.timer4]

if let (current, next) = zip(timers, timers.cycled().dropFirst())
    .first(where: { self[keyPath: $0.0].isActive })
{
    self[keyPath: current].isActive = false
    self[keyPath: next].isActive = true
}

也就是说,我不会那样做。这里有一些微妙的要求应该在一个类型中被捕获。特别是,您假设只有一个活动计时器,但没有任何强制措施。如果这就是你的意思,你应该做一个这样说的类型。例如:

class TimerBank: ObservableObject {
    @Published private(set) var timers: [CGFloat] = []
    @Published private(set) var active: Int?
    var count: Int { timers.count }

    init(timers: [CGFloat]) {
        self.timers = timers
        self.active = timers.startIndex
    }

    func addTimer(timeRemaining: CGFloat = 1000) {
        timers.append(timeRemaining)
    }

    func start(index: Int? = nil) {
        if let index = index {
            active = index
        } else {
            active = timers.startIndex
        }
    }

    func stop() {
        active = nil
    }

    func cycle() {
        if let currentActive = active {
            active = (currentActive + 1) % timers.count
            print("active = \(active)")
        } else {
            active = timers.startIndex
            print("(init) active = \(active)")
        }
    }
}

有了这个,timerBank.cycle() 替换了你的 reduce。

【讨论】:

  • 谢谢 Rob,这是一个很棒的答案!感谢您花时间向我解释。
【解决方案2】:

通过在 Index 上使用模数运算符 (%),我们可以在不压缩的情况下从后到前循环。

let timers = [\Self.timer1, \.timer2, \.timer3, \.timer4]

if let onIndex = timers.firstIndex(where: { self[keyPath: $0].isActive }) {
    self[keyPath: timers[onIndex]].isActive = false

    let nextIndex = (onIndex + 1) % 4 // instead of 4, could use timers.count
    self[keyPath: timers[nextIndex]].isActive = true
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-06-10
    • 2016-08-01
    • 2022-08-23
    • 2016-12-02
    • 1970-01-01
    • 2018-12-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多