I updated today to Xcode 13.3 and now my code can't compile anymore.
I get two error messages. It seems there is a connection between the errors.
First error:
Property 'startAnimation' isolated to global actor 'MainActor' can not be mutated from a non-isolated context
in this line
kuzorra.delay(interval: 1.5) {startAnimation.toggle()
Second error:
Mutation of this property is only permitted within the actor
in this line
@State private var startAnimation = false
I don't really understand how to fix these errors. Any help or hints are welcome :-)
For better understanding my view:
struct AnimationView: View {
@EnvironmentObject var kuzorra: Kuzorra
@AppStorage ("isSoundEnabled") var isSoundEnabled: Bool = true
@State private var startAnimation = false
var label: Bool
var correctAnswer: String
var body: some View {
VStack {
Text(label ? "RICHTIG!" : "FALSCH!")
.foregroundColor(.white)
.font(.largeTitle)
.fontWeight(.bold)
.padding(.horizontal)
.padding(.top)
if !label{ Text("Richtige Antwort: \(correctAnswer)")
.font(.caption)
.lineLimit(1)
.minimumScaleFactor(0.01)
.foregroundColor(.white)
.padding(.horizontal)
}
VStack(alignment: .trailing){
Text(" - Weiter - ")
.padding(.bottom, 5)
.foregroundColor(.white)
.font(.caption2)
}
}
.bgStyle()
.rectangleStyle()
.padding()
.opacity( startAnimation ? 1 : 0)
.rotationEffect(.degrees(startAnimation ? 2880 : 0))
.scaleEffect(startAnimation ? 2 : 1/32)
.animation(Animation.easeOut.speed(1/4), value: startAnimation)
.onTapGesture {
kuzorra.currentPage = .page3
}
.onAppear {
if isSoundEnabled { AudioServicesPlayAlertSound(SystemSoundID(1320))
}
kuzorra.delay(interval: 1.5) {startAnimation.toggle()
}
}
}
}
and here is the code for delay
:
@MainActor
class Kuzorra: ObservableObject {
.
.
.
func delay(interval: TimeInterval, closure: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + interval, execute: closure)
}
There I get the following error message:
Passing non-sendable parameter 'closure' to function expecting a @Sendable closure Parameter 'closure' is implicitly non-sendable.
After pressing fix button this error message disappears..
It is probably prudent to avoid using GCD’s asyncAfter
within Swift concurrency codebase. So, rather than:
.onAppear {
if isSoundEnabled {
AudioServicesPlayAlertSound(SystemSoundID(1320))
}
kuzorra.delay(interval: 1.5) {
startAnimation.toggle()
}
}
Consider using the .task
view modifier (which takes an async
closure, and is also cancelable) and sleep(for:)
(which, unlike traditional sleep
API, does not block the thread):
.task {
if isSoundEnabled {
AudioServicesPlayAlertSound(SystemSoundID(1320))
}
try? await Task.sleep(for: .seconds(1.5))
if !Task.isCancelled {
startAnimation.toggle()
}
}
That achieves asyncAfter
like behavior within Swift concurrency. Needless to say, you could also use do
-try
-catch
pattern:
.task {
if isSoundEnabled {
AudioServicesPlayAlertSound(SystemSoundID(1320))
}
do {
try await Task.sleep(for: .seconds(1.5))
startAnimation.toggle()
} catch {
// print(error)
}
}
If you use Task.sleep
pattern, you either have to try?
and then check isCancelled
or try
and catch
the error.
But the main point is to avoid asyncAfter
in Swift concurrency.