-
-
Save tciuro/ef04030689c29a27e3d2c6dc1bdeaad6 to your computer and use it in GitHub Desktop.
// | |
// ContentView.swift | |
// FormRowHighlightIssue | |
// | |
// Created by Tito Ciuro on 3/27/24. | |
// | |
import SwiftUI | |
enum Route: Hashable { | |
case one | |
case two | |
case three | |
} | |
@Observable | |
final class SomeViewModel { | |
var count: Int = 0 | |
@MainActor | |
func setRandomCount() { | |
count = Int.random(in: 1 ... 99) | |
} | |
} | |
@MainActor | |
struct ContentView: View { | |
@State private var viewModel = SomeViewModel() | |
@State private var navigationPath: [Route] = [] | |
var body: some View { | |
NavigationStack(path: $navigationPath) { | |
Form { | |
Section { | |
NavigationLink(value: Route.one) { | |
Label("\(viewModel.count)", systemImage: "stethoscope") | |
} | |
NavigationLink(value: Route.two) { | |
Label("Two", systemImage: "stethoscope") | |
} | |
} | |
Section { | |
NavigationLink(value: Route.three) { | |
Label("\(viewModel.count)", systemImage: "stethoscope") | |
} | |
} | |
} | |
.navigationDestination(for: Route.self) { route in | |
switch route { | |
case .one: | |
Text("One") | |
case .two: | |
Text("Two") | |
case .three: | |
Text("Three") | |
} | |
} | |
.navigationBarTitleDisplayMode(.inline) | |
.navigationTitle("Selection Issue") | |
.task { | |
viewModel.setRandomCount() | |
} | |
} | |
} | |
} | |
#Preview { | |
ContentView() | |
} |
Got it. If I annotate SomeViewModel
with @MainActor
, I get Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context
in line 26.
So I tried this:
@MainActor
func setRandomCount() {
count = Int.random(in: 1 ... 99)
}
but I'm still seeing the issue.
setRandomCount
, being a function on a MainActor
type is also itself MainActor
.
That warning is basically saying on line 26 you aren't on the MainActor. And that's just bizarre, but part of how SwiftUI is designed, for better or worse. A solution here is to also mark this View as MainActor. In my opinion, all SwiftUI views should always be MainActor isolated. Because anything else is just terribly confusing. Though there actually are other potential non-MainActor use-cases.
This feels like playing whack-a-mole. What a mess. That all SwiftUI views should always be MainActor isolated seems reasonable to me. I wonder why Apple doesn't make that the default, and if anything, have an option to opt-out. All these @MainActor
everywhere feels truly awful. Like... we have no effing clue what we're doing (I'm first in line.)
I wrote about this a little: https://www.massicotte.org/swiftui-isolation
There are things you can do to make this more automatic, including the somewhat extreme option I have here: https://github.com/mattmassicotte/ConcurrencyRecipes/blob/main/Recipes/SwiftUI.md
But I'm afraid I don't think this is related to your original issue....
Did read thoroughly, thanks for the info. Honestly, I hesitate to touch anything without knowing what's going on. I really feel like this is a SwiftUI bug. One thing I've done is add a view state manager I wrote a while ago, similar to Michael Long's solution. It works fine because when the state changes, it redraws the view. But I feel it's a heavy hammer I shouldn't have to be using.
I’m just not sure. And it may be a bug! I just don’t have enough SwiftUI experience to help I’m afraid.
Ok, so I really want to stress here that I know very little about SwiftUI. But my gut is that this isn't a concurrency problem but actually just ordering of operations. You mentioned that this also happens with onAppear, and that makes me think this is even more likely to not be concurrency-related.
However, I do still have a comment about how you are using
task
. You have added@MainActor
on the closure, and that's kind of a red flag to me.body
is always@MainActor
andtask
will inherit the current actor context. So, the annotation is redundant. That's fine, except I always get worried about redundant annotations, not for style, but in case they are exposing a gap in understanding.I also think you may ultimately need to make
SomeViewModel
@MainActor
. But I don't think that is a factor in this specific problem.