Giter VIP home page Giter VIP logo

Comments (2)

davidgarywood avatar davidgarywood commented on June 28, 2024

This is both a limitation, and one of the principles of the pattern itself. The view models create a local state for the view, and therefore create a bridge between view <-> global state in this way. This does mean an element of duplication / "pass the parcel" goes on.

The upside of this is that the view model itself can perform its own modifications on global state where necessary, our view layer remains decoupled from global state, and this gives us a level of flexibility for refactoring.

I tend to create bindings in my view models, rather than use didSet in this way. This can come in handy for throttling calls to services so they don't get called too often when a user's typing, for example:

class LoginViewModel: BaseViewModel<Services>, ObservableObject {
    @Published var searchString: String = ""

    private var cancellables = Set<AnyCancellable>()
  
    override init(services: Services) {
        super.init(services: services)
        self.setBindings()
    }

   private func setBindings() {
        self.$searchString
            .throttle(for: .milliseconds(500), latest: true)
            .sink { [weak self] searchString in
                self?.services.dataManager.search(searchString)
            }
            .store(in: &self.cancellables)           
   }
}

from swiftui-router.

OskarGroth avatar OskarGroth commented on June 28, 2024

Thanks for the explanation, it makes sense and I like the separation from global state that this method brings.
The observation with throttling is a nice touch and does solve a lot of performance issues that developers tend to run into while working with SwiftUI 😄

In my case I wanted two-way observation which is a bit more annoying. There is also a problem in both of our examples that the global state will be assigned upon view model creation, because the initial value given from the observer creation will trigger it to assign it to the global state. So just visiting this view will trigger a global state to update (even if it's to the same value it already was). A dropFirst() can solve this.

I tried cleaning this up a bit and came up with the following:

Letting all my BaseViewModels become ObservableObjects by default.
Define var cancellables and an empty setupBindings() on the superclass.

open class BaseViewModel<S>: ObservableObject {
    
    public var services: S
    
    var cancellables = Set<AnyCancellable>()
    
    public init(services: S) {
        self.services = services
        setupBindings()
    }
    
    func setupBindings() {
        // Override in subclass
    }
        
}

Add a Publisher extension specifically for the use case of binding view model state to global state, with optional throttling:

extension Publisher where Output: Equatable, Failure == Never {
    
    func assignViewModelState<Root>(to keyPath: ReferenceWritableKeyPath<Root, Self.Output>, on object: Root, throttle: Bool = false) -> AnyCancellable {
        self
            .dropFirst()
            .throttle(for: throttle ? .milliseconds(200) : 0, scheduler: RunLoop.main, latest: true)
            .removeDuplicates()
            .assign(to: keyPath, on: object)
    }
    
}

Then the view model becomes:

class AccountScreenViewModel: BaseViewModel<Services> {
    
    @Published var isAway: Bool = false

    override func setupBindings() {
        services.loginManager.state.$isAway.assign(to: &$isAway)
        $isAway
            .assignViewModelState(to: \.isAway, on: services.loginManager.state)
            .store(in: &cancellables)
    }
    
}

It imposes a bit more on the View Model, but this is such a recurring use case that I think it's warranted. Seems to work pretty well so far. What do you think?

You should add a section about handling views that require Binding to the blog post when you update it next time 👍

from swiftui-router.

Related Issues (5)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.