import Combine import SwiftUI /// A `ViewStore` is an object that can observe state changes and send actions. They are most /// commonly used in views, such as SwiftUI views, UIView or UIViewController, but they can be /// used anywhere it makes sense to observe state and send actions. /// /// In SwiftUI applications, a `ViewStore` is accessed most commonly using the `WithViewStore` view. /// It can be initialized with a store and a closure that is handed a view store and must return a /// view to be rendered: /// /// var body: some View { /// WithViewStore(self.store) { viewStore in /// VStack { /// Text("Current count: \(viewStore.count)") /// Button("Increment") { viewStore.send(.incrementButtonTapped) } /// } /// } /// } /// /// In UIKit applications a `ViewStore` can be created from a `Store` and then subscribed to for /// state updates: /// /// let store: Store /// let viewStore: ViewStore /// /// init(store: Store) { /// self.store = store /// self.viewStore = ViewStore(store) /// } /// /// func viewDidLoad() { /// super.viewDidLoad() /// /// self.viewStore.publisher.count /// .sink { [weak self] in self?.countLabel.text = $0 } /// .store(in: &self.cancellables) /// } /// /// @objc func incrementButtonTapped() { /// self.viewStore.send(.incrementButtonTapped) /// } /// @dynamicMemberLookup public final class ViewStore: ObservableObject { /// A publisher of state. public let publisher: StorePublisher private var viewCancellable: AnyCancellable? /// Initializes a view store from a store. /// /// - Parameters: /// - store: A store. /// - isDuplicate: A function to determine when two `State` values are equal. When values are /// equal, repeat view computations are removed. public init( _ store: Store, removeDuplicates isDuplicate: @escaping (State, State) -> Bool ) { let publisher = store.$state.removeDuplicates(by: isDuplicate) self.publisher = StorePublisher(publisher) self.state = store.state self._send = store.send self.viewCancellable = publisher.sink { [weak self] in self?.state = $0 } } /// The current state. @Published public internal(set) var state: State let _send: (Action) -> Void /// Returns the resulting value of a given key path. public subscript(dynamicMember keyPath: KeyPath) -> LocalState { self.state[keyPath: keyPath] } /// Sends an action to the store. /// /// `ViewStore` is not thread safe and you should only send actions to it from the main thread. /// If you are wanting to send actions on background threads due to the fact that the reducer /// is performing computationally expensive work, then a better way to handle this is to wrap /// that work in an `Effect` that is performed on a background thread so that the result can /// be fed back into the store. /// /// - Parameter action: An action. public func send(_ action: Action) { self._send(action) } /// Derives a binding from the store that prevents direct writes to state and instead sends /// actions to the store. /// /// The method is useful for dealing with SwiftUI components that work with two-way `Binding`s /// since the `Store` does not allow directly writing its state; it only allows reading state and /// sending actions. /// /// For example, a text field binding can be created like this: /// /// struct State { var name = "" } /// enum Action { case nameChanged(String) } /// /// TextField( /// "Enter name", /// text: viewStore.binding( /// get: { $0.name }, /// send: { Action.nameChanged($0) } /// ) /// ) /// /// - Parameters: /// - get: A function to get the state for the binding from the view /// store's full state. /// - localStateToViewAction: A function that transforms the binding's value /// into an action that can be sent to the store. /// - Returns: A binding. public func binding( get: @escaping (State) -> LocalState, send localStateToViewAction: @escaping (LocalState) -> Action ) -> Binding { Binding( get: { get(self.state) }, set: { newLocalState, transaction in withAnimation(transaction.disablesAnimations ? nil : transaction.animation) { self.send(localStateToViewAction(newLocalState)) } }) } /// Derives a binding from the store that prevents direct writes to state and instead sends /// actions to the store. /// /// The method is useful for dealing with SwiftUI components that work with two-way `Binding`s /// since the `Store` does not allow directly writing its state; it only allows reading state and /// sending actions. /// /// For example, an alert binding can be dealt with like this: /// /// struct State { var alert: String? } /// enum Action { case alertDismissed } /// /// .alert( /// item: self.store.binding( /// get: { $0.alert }, /// send: .alertDismissed /// ) /// ) { alert in Alert(title: Text(alert.message)) } /// /// - Parameters: /// - get: A function to get the state for the binding from the view store's full state. /// - action: The action to send when the binding is written to. /// - Returns: A binding. public func binding( get: @escaping (State) -> LocalState, send action: Action ) -> Binding { self.binding(get: get, send: { _ in action }) } /// Derives a binding from the store that prevents direct writes to state and instead sends /// actions to the store. /// /// The method is useful for dealing with SwiftUI components that work with two-way `Binding`s /// since the `Store` does not allow directly writing its state; it only allows reading state and /// sending actions. /// /// For example, a text field binding can be created like this: /// /// struct State { var name = "" } /// enum Action { case nameChanged(String) } /// /// TextField( /// "Enter name", /// text: viewStore.binding( /// send: { Action.nameChanged($0) } /// ) /// ) /// /// - Parameters: /// - localStateToViewAction: A function that transforms the binding's value /// into an action that can be sent to the store. /// - Returns: A binding. public func binding( send localStateToViewAction: @escaping (State) -> Action ) -> Binding { self.binding(get: { $0 }, send: localStateToViewAction) } /// Derives a binding from the store that prevents direct writes to state and instead sends /// actions to the store. /// /// The method is useful for dealing with SwiftUI components that work with two-way `Binding`s /// since the `Store` does not allow directly writing its state; it only allows reading state and /// sending actions. /// /// For example, an alert binding can be dealt with like this: /// /// struct State { var alert: String? } /// enum Action { case alertDismissed } /// /// .alert( /// item: self.store.binding( /// send: .alertDismissed /// ) /// ) { alert in Alert(title: Text(alert.message)) } /// /// - Parameters: /// - action: The action to send when the binding is written to. /// - Returns: A binding. public func binding(send action: Action) -> Binding { self.binding(send: { _ in action }) } } extension ViewStore where State: Equatable { public convenience init(_ store: Store) { self.init(store, removeDuplicates: ==) } }