I've been trying to use the new onKeyPress
functionality in SwiftUI 5. However, updates to a @Published
property of an Observable Object within the handler produce a warning "Publishing changes from within view updates is not allowed, this will cause undefined behavior."
Note I haven't seen anything actually go wrong in practice, but Xcode logs a lot of red warnings in Console and purple runtime issues.
A simple replication; can modify with button or modify local @State
without the warning; however modifying the @Published
property from onKeyPress
produces the warning.
import SwiftUI
@MainActor
class ExampleService: ObservableObject {
@Published var text = ""
}
struct ContentView: View {
@StateObject var service = ExampleService()
@State var localText = ""
var body: some View {
VStack {
Button {
// This is fine
service.text += "!"
} label: {
Text("Press Me")
}
Label("Example Focusable", systemImage: "arrow.right")
.focusable()
.onKeyPress { action in
// XCode whines about "Publishing changes from within view updates is not allowed, this will cause undefined behavior."
service.text += "."
return .handled
}
Label("Example Local State", systemImage: "arrow.left")
.focusable()
.onKeyPress { action in
// This is fine
localText += "."
return .handled
}
Text(service.text)
Text(localText)
}
}
}
#Preview {
ContentView()
}
To make the warning disappear, use
DispatchQueue.main.async {
service.text += "."
}
that will ensure the UI update is carried out on the main thread, as required by SwiftUI. Note, as mentioned in the comments you can also use Task{...}
.
As I understand it, @MainActor
is supposed to make execution on the main queue,
but (I guess) not always. See this proposal to remove it from Swift 6:
https://github.com/apple/swift-evolution/blob/main/proposals/0401-remove-property-wrapper-isolation.md