In the tutorial from Ray that I'm following I have the following properties set
struct ContentView : View {
var rTarget = Double.random(in: 0..<1)
var gTarget = Double.random(in: 0..<1)
var bTarget = Double.random(in: 0..<1)
}
These are of course immutable so I can't modify them from a func unless I mark that func as mutating
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
But I call this func from 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
But I can't mark var body
as mutating
var
'mutating' may only be used on 'func' declarations
So at this point, I want to reset the xTarget
values each time the user taps on the alert button and I don't know what a good practice will be at this point.
Essentially, a function that’s been marked as mutating can change any property within its enclosing value. The word “value” is really key here, since Swift’s concept of structured mutations only applies to value types, not to reference types like classes and actors.
In swift, classes are reference type whereas structures and enumerations are value types. The properties of value types cannot be modified within its instance methods by default. In order to modify the properties of a value type, you have to use the mutating keyword in the instance method.
Although the concept of separating mutating and non-mutating APIs is something that’s unique to value types, we can still make a mutating function a part of a protocol as well — even if that protocol might end up being adopted by a reference type, such as a class.
One of the ways in which Swift helps us write more robust code is through its concept of value types, which limit the way that state can be shared across API boundaries.
I was working on the same article.
I didn't get this problem because I was already using the @State
property wrapper.
As suggested by kontiki Session 226 (Data Flow Through SwiftUI) is great for understanding how to use which property wrapper when you are updating the data different source.
And If you want to know what is @State this answer has it all.
Here is my code:
@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
This question is old, but it's kinda hard to follow and to understand the answers without more context. The question comes from RayWenderlich - SwiftUI: Getting Started.
You need to do two things in order to reset the game after the alert is presented:
mutating
from your function displayAlert()
@State
in front of the variables you wish to modify (i.e. rTarget
, gTarget
, bTarget
)Full code for reference - Note that I reset the game using 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
}
}
Only solution I found at this point is to also mark the xTarget
props as @State
and modify them without mutating 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)
But it's not clear to me that it's good practice.
It is hard to tell what is recommended in your case, because in your example you are not showing us what the target variables are actually for.
However, I think it is safe to say that in SwiftUI views, variables that need to change over time, must be either @State or of one of the binding types available to you. Otherwise, it most likely need to be unmutable.
It all comes down to determine what is the "source of truth". If those targets are something internal to the view, then use @State, if the source of truth is external to the view, you would go for one of the bindable options.
I strongly recommend you invest the 37 minutes it takes to watch WWDC2019, Session 226 (Data Flow Through SwiftUI). It will clear all your questions about this.
If you are in a hurry, jump to 5:45. But I do recommend you watch the whole thing. It will save you time eventually.
If you don't know what the "source of truth" is. Then you should definitely must watch the session.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With