In the following simplified code, we're getting stuck with MainActor being both forbidden and required. We have a ViewModifier that observes a ObservableObject. Therefore it must be MainActor (and in fact, becomes MainActor automatically due to the ObservedObject wrapper).
However, it is used within a ButtonStyle. ButtonStyle requires a non-isololated makeBody
method. But if it's nonisolated, then it can't access the ViewModifier. Under "complete" concurrency, the following either generates "Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context" or ""Main actor-isolated instance method 'makeBody(configuration:)' cannot be used to satisfy nonisolated protocol requirement."
My belief is that this is a SwiftUI bug. If View.body
is MainActor, ViewModifier.body(content:)
should be as well. But until SwiftUI changes, or in the case that it never changes, is there any way out of this corner?
// A global "configuration" class that things can observe to turn on and off the debugOverlay
// It is MainActor to ensure @Published only emits on the main actors, and to permit the create of `.global`
@MainActor
final class Config: ObservableObject {
static let global: Config = .init()
@Published public var debugOverlayEnabled = false
}
// An overlay that displays whenever the global debugOverlayEnabled changes
// Using @ObservedObject quietly makes the MainActor
private struct DebugComponentOverlay: ViewModifier {
@ObservedObject var globalConfiguration = Config.global
func body(content: Content) -> some View {
content.overlay {
if globalConfiguration.debugOverlayEnabled {
Color.green.opacity(0.25)
}
}
}
}
// Now we're stuck. This needs to be MainActor in order to use DebugComponentOverlay,
// but cannot be MainActor and still conform to `ButtonStyle`
// "Main actor-isolated instance method 'makeBody(configuration:)' cannot be used to satisfy nonisolated protocol requirement
struct LinkButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
Button("Text") {}
.modifier(DebugComponentOverlay())
}
}