Using await keyword in SwiftUI's ViewModifier

61 Views Asked by At

There is a Model made with Actor. ButtonView changes variables in Model. ColorView displays colors differently depending on the model variable values.

But this example doesn't work. Because await keyword cannot be used in ViewModifier.

Is this problem solvable? What am I missing?

Thank you for reading.

actor ActorModel: ObservableObject {
    static let shared = ActorModel()
    private init() {}
    
    @Published var isActive: Bool = false
    
    func toggleActive() {
        isActive.toggle()
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            ColorView()
            ButtonView()
        }
        .padding()
    }
}

struct ColorView: View {
    @ObservedObject private var model = ActorModel.shared
    
    var body: some View {
        VStack {
            Rectangle()
                .foregroundStyle(await model.isActive ? .red : .blue) // <-- Problem!
                .frame(width: 100, height: 100)
        }
        .padding()
    }
}

struct ButtonView: View {
    @ObservedObject private var model = ActorModel.shared
    
    var body: some View {
        VStack {
            Button("Toggle Color") {
                Task {
                    await model.toggleActive()
                }
            }
        }
        .padding()
    }
}
1

There are 1 best solutions below

1
Pierre Janineh On BEST ANSWER

If you must use an actor then you should do something like this:

struct ColorView: View {
    @ObservedObject private var model = ActorModel.shared

    @State private var color: Color = .gray //Default
    
    var body: some View {
        VStack {
            Rectangle()
                .foregroundStyle(color) // <-- Problem!
                .frame(width: 100, height: 100)
        }
        .padding()
        .onAppear {
            Task {
                color = await model.isActive ? .red : .blue
            }
        }
    }
}

Although, @Published attribute has no meaning here. As you won't be getting updates from an anctor.

If you need to subscribe to updates then you'd try another approach:

struct ColorView: View {
    @ObservedObject private var model = ActorModel.shared
    @State private var isActiveState: Bool = false

    var body: some View {
        VStack {
            // ...
        }
        .onAppear {
            Task {
                await subscribeToModelChanges()
            }
        }
    }

    private func subscribeToModelChanges() async {
        await model.subscribeToIsActiveChanges { isActive in
            self.isActiveState = isActive
        }
    }
}

actor ActorModel: ObservableObject {
    // ...

    func subscribeToIsActiveChanges(_ onChange: @escaping (Bool) -> Void) async {
        // Logic to notify the subscriber about changes
        // This part is tricky because Actor doesn't have a built-in observation mechanism
        // You might need to implement your own mechanism to notify about changes
    }
}