SwiftUI 从 var body 调用 mutating func

SwiftUI call mutating func from var body

在我关注的 Ray 教程中,我设置了以下属性

struct ContentView : View {

    var rTarget = Double.random(in: 0..<1)
    var gTarget = Double.random(in: 0..<1)
    var bTarget = Double.random(in: 0..<1)
}

这些当然是不可变的,所以我不能从函数修改它们,除非我将那个函数标记为变异

func reset() {
    rTarget = Double.random(in: 0..<1)
    gTarget = Double.random(in: 0..<1)
    bTarget = Double.random(in: 0..<1)
}

Cannot assign to property: 'self' is immutable

但是我从 var body

调用这个函数
mutating func reset() {
    rTarget = Double.random(in: 0..<1)
    gTarget = Double.random(in: 0..<1)
    bTarget = Double.random(in: 0..<1)
}

fileprivate mutating func displayAlert() -> Alert {
    return Alert(title: Text("Your Score"), message: Text("\(computeScore())"), dismissButton: Alert.Button.destructive(Text("Ok"), onTrigger: {
        self.reset()
    }))
}

 var body: some View {
    Button(action: {
        self.showAlert = true
    }) {
        Text("Hit Me!")
        }.presentation($showAlert) {
            displayAlert()
    }
}

Cannot use mutating member on immutable value: 'self' is immutable

但我无法将 var body 标记为 mutating var

'mutating' may only be used on 'func' declarations

所以在这一点上,我想在每次用户点击警报按钮时重置 xTarget 值,我不知道此时有什么好的做法。

此时我找到的唯一解决方案是将 xTarget 道具也标记为 @State 并修改它们而不改变 funcs

@State private var rTarget = Double.random(in: 0..<1)
@State private var gTarget = Double.random(in: 0..<1)
@State private var bTarget = Double.random(in: 0..<1)

但我不清楚这是个好习惯。

很难说出在你的情况下推荐什么,因为在你的例子中你没有向我们展示目标变量的实际用途。

但是,我认为可以肯定地说,在 SwiftUI 视图中,需要随时间变化的变量必须是 @State 或您可用的绑定类型之一。否则,它很可能需要是不可变的。

这一切都归结为确定什么是"source of truth"。如果这些目标是视图内部的东西,那么使用@State,如果真实来源在视图外部,您将选择可绑定选项之一。

我强烈建议您花 37 分钟观看 WWDC2019,Session226(通过 SwiftUI 的数据流)。它将清除您对此的所有问题。

如果你赶时间,跳到5:45。但我确实建议你观看整个过程。它最终会为您节省时间。

如果您不知道 "source of truth" 是什么。那你一定要看session.

我正在写同一篇文章。

我没有遇到这个问题,因为我已经在使用 @State 属性 包装器。 正如 kontiki 所建议的那样,会话 226(通过 SwiftUI 的数据流)非常有助于理解在更新不同来源的数据时如何使用哪个 属性 包装器。

如果你想知道什么是@State, 应有尽有。

这是我的代码:

@State var rTarget = Double.random(in: 0..<1)
@State var gTarget = Double.random(in: 0..<1)
@State var bTarget = Double.random(in: 0..<1)
@State var rGuess: Double
@State var gGuess: Double
@State var bGuess: Double

这个问题很老,但如果没有更多上下文,就很难理解和理解答案。问题来自RayWenderlich - SwiftUI: Getting Started.

您需要做两件事才能在出现警报后重置游戏:

  1. 从您的函数中删除 mutating displayAlert()
  2. 在你要修改的变量前面加上@State(即rTargetgTargetbTarget

完整代码供参考 - 请注意,我使用 func resetGame()

重置了游戏
import SwiftUI

struct ContentView: View {
    // In SwiftUI, when a @State variable changes,
    // the view invalidates its appearance and recomputes the body.
    @State var randomRed = Double.random(in: 0..<1)
    @State var randomGreen = Double.random(in: 0..<1)
    @State var randomBlue = Double.random(in: 0..<1)

    @State var redGuess: Double
    @State var greenGuess: Double
    @State var blueGuess: Double

    @State var showAlert: Bool = false

    func calculateScore() -> String {
        // The diff value is just the distance between two points in three-dimensional space.
        // You subtract it from 1, then scale it to a value out of 100.
        // Smaller diff yields a higher score.
        let redDiff = redGuess - randomRed
        let greenDiff = greenGuess - randomGreen
        let blueDiff = blueGuess - randomBlue
        let diff = sqrt(redDiff * redDiff + greenDiff * greenDiff + blueDiff * blueDiff)

        return "\(Int((1.0 - diff) * 100.0 + 0.5))"
    }

     func resetGame() {
        randomRed = Double.random(in: 0..<1)
        randomGreen = Double.random(in: 0..<1)
        randomBlue = Double.random(in: 0..<1)

        redGuess = 0.5
        greenGuess = 0.5
        blueGuess = 0.5
    }

    var body: some View {
        VStack {
            HStack {
                VStack {
                    Color(red: randomRed, green: randomGreen, blue: randomBlue)
                    Text("Match this color")
                }
                VStack {
                    Color(red: redGuess, green: greenGuess, blue: blueGuess)
                    Text("R: \(Int(redGuess * 255))  G: \(Int(greenGuess * 255))  B: \(Int(blueGuess * 255))")
                }
            }

            Button(action: {self.showAlert = true} ) {
                Text("Hit me")
            }.alert(isPresented: $showAlert, content: {
                Alert(title: Text("Your Score"), message: Text(self.calculateScore()),
                      dismissButton: Alert.Button.default(Text("OK"), action: { self.resetGame()
                }))
            }).padding()

            ColorSlider(value: $redGuess, textColor: .red)
            ColorSlider(value: $greenGuess, textColor: .green)
            ColorSlider(value: $blueGuess, textColor: .blue)
        }
    }
}

struct ColorSlider: View {
    // Use @Binding instead of @State, because the ColorSlider view
    // doesn't own this data—it receives an initial value from its parent view and mutates it.
    @Binding var value: Double
    var textColor: Color

    var body: some View {
        HStack {
            Text("0").foregroundColor(textColor)
            Slider(value: $value)
            Text("255").foregroundColor(textColor)
        }.padding(.horizontal)  // Add some space before & after the text
    }
}