为什么每次 View 的父 View 的 @State 变量更改时,视图的 body 属性中的代码都不会运行?



我希望在应用程序首次加载时运行函数calculateBedtime((,并且每次ContentView的任何@State变量发生变化时,都会运行该函数,以便在应用程序底部最下面的部分不断显示更新的就寝时间。然而,该应用程序的作用就好像可变就寝时间一直保持其初始值,永远不会改变。

我希望发生的是,当我更改任何@State变量时,比如使用DatePicker更改wakeUp时,body属性会被重新调用,其中的第一行是对calculateBedtime((的调用,因此该函数会根据我的意愿频繁地运行和更新就寝时间。

import SwiftUI
struct ContentView: View {
@State private var wakeUp = defaultWakeTime
@State private var bedtime = ""
@State private var sleepAmount = 8.0
@State private var coffeeAmount = 1

@State private var alertTitle = ""
@State private var alertMessage = ""
@State private var showingAlert = false


var body: some View {
bedtime = calculateBedtime()

return NavigationView
{
Form
{
Section(header:  Text("When do you want to wake up?").font(.headline))
{
Text("When do you want to wake up?")
.font(.headline)

DatePicker("Please enter a time", selection: $wakeUp, displayedComponents: .hourAndMinute)
.labelsHidden()
.datePickerStyle(WheelDatePickerStyle())
}

Section(header: Text("Desired amount of sleep")
.font(.headline))
{
Stepper(value: $sleepAmount, in: 4...12, step: 0.25)
{
Text("(sleepAmount, specifier: "%g") hours")
}
}

Section(header: Text("Daily coffee intake")
.font(.headline))
{
Picker("(coffeeAmount+1) cup(s)", selection: $coffeeAmount)
{
ForEach(1..<21)
{ num in
if num==1
{
Text("(num) cup")
}
else
{
Text("(num) cups")
}
}
}
.pickerStyle(MenuPickerStyle())
}

Section(header: Text("Your Ideal Bedtime")
.font(.headline))
{
Text("(bedtime)")
.font(.largeTitle)
}
}
.navigationBarTitle("BetterRest")
}
/*.onAppear(perform: {
calculateBedtime()
})
.onChange(of: wakeUp, perform: { value in
calculateBedtime()
})
.onChange(of: sleepAmount, perform: { value in
calculateBedtime()
})
.onChange(of: coffeeAmount, perform: { value in
calculateBedtime()
})*/
}
static var defaultWakeTime: Date
{
var components = DateComponents()
components.hour = 7
components.minute = 0
return Calendar.current.date(from: components) ?? Date()
}

func calculateBedtime() -> String
{
let model = SleepCalculator()

let components = Calendar.current.dateComponents([.hour, .minute], from: wakeUp)
let hour = (components.hour ?? 0) * 60 * 60
let minute = (components.minute ?? 0) * 60
var sleepTime = ContentView.defaultWakeTime

do
{
let prediction = try
model.prediction(wake: Double(hour + minute), estimatedSleep: sleepAmount, coffee: Double(coffeeAmount))

sleepTime = wakeUp - prediction.actualSleep

let formatter = DateFormatter()
formatter.timeStyle = .short

alertMessage = formatter.string(from: sleepTime)
alertTitle = "Your ideal bedtime is..."

} catch {
alertTitle = "Error"
alertMessage = "Sorry, there was a problem calculating your bedtime."
}

showingAlert = true
return alertMessage
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}

这里有什么问题?我是SwiftUI的新手,我觉得我一定对@State包装器的工作方式有一个严重的误解。什么是获得我想要的行为的好方法?

@State变量只能从视图主体及其调用的方法中进行突变;对于其他任何事情,您需要使用ObservableObject,我认为这将解决您的问题。

您应该只从视图主体内部或从其调用的方法访问状态属性。因此,请将您的状态属性声明为私有属性,以防止视图的客户端访问它们。从任何线程更改状态属性都是安全的。

下面代码的脚手架或多或少应该能达到你想要的结果:

class SleepTimerViewModel: ObservableObject {
@Published public var bedTimeMessage: String?
}
struct ContentView: View {
@ObservedObject public var sleepTimerViewModel: SleepTimerViewModel
var body: some View {
Text(sleepTimerViewModel.bedTimeMessage) 
}

public func updateBedTimeMessage() {
sleepTimerViewModel.bedTimeMessage = "Hello World"
}
}

我确实觉得Swift不想让你知道你更新@State变量不正确,这有点烦人。它只是默默地忽略你试图设置的值,这太烦人了!

相关内容

最新更新