07-18-2023, 09:18 PM
I have an `ObservableObject` class and a SwiftUI view. When a button is tapped, I create a [`Task`](
```
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:
<img src="https://i.stack.imgur.com/TnDMi.gif" width="200" alt="Rotation animation freezes when the button is pressed">
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:
<img src="https://i.stack.imgur.com/kqkuK.gif" width="200" alt="Rotation animation continues even when the button is pressed">
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.
[To see links please register here]
) 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:
<img src="https://i.stack.imgur.com/TnDMi.gif" width="200" alt="Rotation animation freezes when the button is pressed">
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:
<img src="https://i.stack.imgur.com/kqkuK.gif" width="200" alt="Rotation animation continues even when the button is pressed">
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.