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.
您需要做两件事才能在出现警报后重置游戏:
- 从您的函数中删除
mutating
displayAlert()
- 在你要修改的变量前面加上
@State
(即rTarget
、gTarget
、bTarget
)
完整代码供参考 - 请注意,我使用 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
}
}
在我关注的 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.
您需要做两件事才能在出现警报后重置游戏:
- 从您的函数中删除
mutating
displayAlert()
- 在你要修改的变量前面加上
@State
(即rTarget
、gTarget
、bTarget
)
完整代码供参考 - 请注意,我使用 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
}
}