I have an ObservableObject class and a SwiftUI view. When a button is tapped, I create a Task and call populate (an async function) from within it. I thought this would execute populate on a background thread but instead the entire UI freezes. Here's my code:
class ViewModel: ObservableObject {
@Published var items = [String]()
func populate() async {
var items = [String]()
for i in 0 ..< 4_000_000 { /// this usually takes a couple seconds
items.append("\(i)")
}
self.items = items
}
}
struct ContentView: View {
@StateObject var model = ViewModel()
@State var rotation = CGFloat(0)
var body: some View {
Button {
Task {
await model.populate()
}
} label: {
Color.blue
.frame(width: 300, height: 80)
.overlay(
Text("\(model.items.count)")
.foregroundColor(.white)
)
.rotationEffect(.degrees(rotation))
}
.onAppear { /// should be a continuous rotation effect
withAnimation(.easeInOut(duration: 2).repeatForever()) {
rotation = 90
}
}
}
}
Result:
The button stops moving, then suddenly snaps back when populate finishes.
Weirdly, if I move the Task into populate itself and get rid of the async, the rotation animation doesn't stutter so I think the loop actually got executed in the background. However I now get a Publishing changes from background threads is not allowed warning.
func populate() {
Task {
var items = [String]()
for i in 0 ..< 4_000_000 {
items.append("\(i)")
}
self.items = items /// Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.
}
}
/// ...
Button {
model.populate()
}
Result:
How can I ensure my code gets executed on a background thread? I think this might have something to do with MainActor but I'm not sure.


awaitmeans that you wait for theasyncfunction to return you a result (which obviously blocks the current thread, which is main). In your second option you run the task on a background thread, so you need to ensure that results of the task are delivered to the main thread. The error you see hints you about this and provides an example, how to achieve this. How do you deliver items to the UI? Is it just a property on an observable object?Tasks asynchronous? When Iawaitinside aTaskshouldn't the current thread be a background thread? I can't figure out the difference between the first and second options. Anyway in SwiftUI all you need to do is set the property and the UI automatically updates.MainActor.run { self.items = items }to move the update to the main thread?