SwiftUI and Tasks?

Hello -

I've been experimenting with async/await and Task now that they're available for iOS 14 and as part of my experimenting I've been trying to get the following code working

import SwiftUI

struct ContentView: View {
    @State var foo: Int
    
    var body: some View {
        if foo == 0 {
            Text("Hello, world!")
        } else {
            Text("Goodbye!")
        }
    }
    
    init() {
        foo = 0
        setup()
    }
  
    func setup() {
        Task {
            try? await Task.sleep(nanoseconds: 3_000_000_000)
            await MainActor.run { foo = 1 }
        }
    }
}

When I run it in the simulator the text never switches over to "Goodbye!". The code compiles without any errors and using break points I can see that all the code runs as expected but the text doesn't update. If I change the code to the following

struct ContentView: View {
    @State var foo: Int
    
    var body: some View {
        Group {
            if foo == 0 {
                Text("Hello, world!")
            } else {
                Text("Goodbye!")
            }
        }
        .task {
            try? await Task.sleep(nanoseconds: 3_000_000_000)
            foo = 1
        }
    }
    
    init() {
        foo = 0
    }
}

The code works as expected and "Hello, world!" switches to "Goodbye!". Unfortunately the .task modifier is only available for iOS 15 and the code I'm working with needs to be compatible with iOS 14.

I'd love to understand why the first example doesn't work and if there is a way to make it work without having to create an ObservableObject to hold foo

Thanks in advance - AYAL

The .task modifier just uses onAppear/onDisappear under the hood so it's quite easy to replicate. You can check this package by John Sundell.

struct ContentView: View {
    @State var foo: Int
    
    var body: some View {
        if foo == 0 {
            Text("Hello, world!")
        } else {
            Text("Goodbye!")
        }
    }
    
    init() {
        foo = 0
        setup()
    }
  
    func setup() {
        Task {
            try? await Task.sleep(nanoseconds: 3_000_000_000)
            await MainActor.run { 
                 foo = 1
                 print(foo) // 0, foo dose not change to 1, so the view will not change
            }
        }
    }
}

Not change @State at init.

THANK YOU!! That definitely solve the "how do I make this work?"

Yes, very true. The question is "why doesn't foo change?". Shouldn't it? and if it shouldn't then why doesn't the compiler complain?