0

I’m looking for the proper pattern and syntax to address my goal of having an ObservableObject instance that I can share amongst multiple views, but while keeping logic associated with it contained to another class. I’m looking to do this to allow different ‘controller’ classes to manipulate the properties of the state without the view needing to know which controller is acting on it (injected).

Here is a simplification that illustrates the issue:

import SwiftUI

class State: ObservableObject {
    @Published var text = "foo"
}

class Controller {
    var state : State
    init(_ state: State) {
        self.state = state
    }
    
    func changeState() {
        state.text = "bar"
    }
}

struct ContentView: View {
    @StateObject var state = State()
    var controller: Controller!
    
    init() {
        controller = Controller(state)
    }
    
    var body: some View {
        VStack {
            Text(controller.state.text) // always shows 'foo'
            Button("Press Me") {
                print(controller.state.text) // prints 'foo'
                controller.changeState()
                print(controller.state.text) // prints 'bar'
            }
        }
    }
}

I know that I can use my ObservableObject directly and manipulate its properties such that the UI is updated in response, but in my case, doing so prevents me from having different ‘controller’ instances depending on what needs to happen. Please advise with the best way to accomplish this type of scenario in SwiftUI

2
  • 1
    SwiftUI is designed to use MVVM pattern (ObservableObject is view model here), and you try to use MVC which is foreign here. I will not ask why, just don't do that - it is anti-pattern. Commented Mar 24, 2022 at 6:08
  • Take a look at protocols/generics instead of using objects. Commented Mar 24, 2022 at 8:24

1 Answer 1

2

To make SwiftUI view follow state updates, your controller needs to be ObservableObject.

SwiftUI view will update when objectWillChange is triggered - it's done automatically for properties annotated with Published, but you can trigger it manually too.

Using same publisher of your state, you can sync two observable objects, for example like this:

class Controller: ObservableObject {
    let state: State
    private var cancellable: AnyCancellable?
    init(_ state: State) {
        self.state = state

        cancellable = state.objectWillChange.sink {
            self.objectWillChange.send()
        }
    }

    func changeState() {
        state.text = "bar"
    }
}

struct ContentView: View {
    @StateObject var controller = Controller(State())
Sign up to request clarification or add additional context in comments.

2 Comments

Seems to me this is reimplementing what SwiftUI does automatically for us when we use structs
@malhal yes, struct is much different from class. with struct it can work automatically, because changing struct property actually creates a new "object", but with class it won't work out of the box.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.