我正在努力学习Swift,并且已经完成了几个教程,但是,我似乎在兜圈子,需要一些关于如何解决这个问题的指导。
我的目标是创建一个递减计时器,给定用户输入(以秒为单位(,在这种情况下,我选择了一个Stepper来-/+值。然后开始在按下按钮时递减计时器,在这种情况下为"定时器";递减";。计数器显示在标签上。
如果我硬编码起始值,这个问题非常容易,但这对UI测试有什么作用呢。所以,这就是";具有挑战性的";我能够想到的任务是帮助理解SwiftUI是如何工作的。
我遇到的问题是用户传递的变量是不可变的。我尝试过复制它,或者将它分配给其他变量进行操作,但似乎是在兜圈子。朝着正确的方向推动或潜在的解决方案会有很大的帮助。
struct ContentView: View {
@State private var timeInput: Int = 0
var timer = Timer()
var timeInputCopy: Int {
timeInput
}
var body: some View {
Stepper("Input", value: $timeInput, in: 0...150)
Button("Decrement", action: decrementFunction)
Label(String(timeInputCopy), image: "")
.labelStyle(TitleOnlyLabelStyle())
}
func decrementFunction() {
timer.invalidate()
timer = Timer.schedulerTimer(timeInterval: 1,
target: self,
selector: #selector(ContentView.timerClass),
userInfo: nil,
repeats: true)
}
func timerClass() {
timeInputCopy -= timeInputCopy
if (timeInputCopy == 0) {
timer.invalidate()
}
}
> Cannot assign to property: 'self' is immutable
> Mark method 'mutating' to make 'self' mutable
试图按照Xcode的建议进行自动修复并不能带来有效的解决方案。我觉得我错过了一个核心原则。
正如我在上面的评论中所提到的:
-
timeInputCopy
没有意义——它不是真正的副本,它只是一个返回timeInput
的计算属性 -
在SwiftUI中使用带有选择器的
Timer
形式不会有太大的运气。相反,看看Timer
发布者。
这里有一个解决方案:
import Combine
import SwiftUI
class TimerManager : ObservableObject {
@Published var timeRemaining = 0
private var cancellable : AnyCancellable?
func startTimer(initial: Int) {
timeRemaining = initial
cancellable = Timer.publish(every: 1, on: .main, in: .common)
.autoconnect()
.sink { _ in
self.timeRemaining -= 1
if self.timeRemaining == 0 {
self.cancellable?.cancel()
}
}
}
}
struct ContentView: View {
@StateObject private var timerManager = TimerManager()
@State private var stepperValue = 60
var body: some View {
Stepper("Input (stepperValue)", value: $stepperValue, in: 0...150)
Button("Start") {
timerManager.startTimer(initial: stepperValue)
}
Label("(timerManager.timeRemaining)", image: "")
.labelStyle(TitleOnlyLabelStyle())
}
}
这可以全部在View
中完成,但使用ObservableObject
可以很好地分离管理定时器的状态和UI的状态。