RxSwift is a Swift implementation of ReactiveX. Amongst
other things, it allows you to easily bind properties to user interface
elements.
Using those two frameworks you can make very nice app architectures.
RxSwift allows you to bind application state to your UI, and ReSwift
emits state updates in response to actions. We take it one step further
though.
Similar to react-redux, ReRxSwift allows you to create view
controllers that have props and actions. View controllers read all
data they need from their props (instead of directly from the state),
and they change data by invoking callbacks defined by actions (instead
of directly dispatching ReSwift actions). This has some nice advantages:
Better separation of concerns. It is easier to understand what your
view controller does and what data it uses. In other words, it
facilitates local reasoning.
Unit-testing. Because of the separation of concerns, you can easily
unit-test your view controllers, all the way up to the Interface
Builder connections.
Better reusability. Reusing your view controllers is as simple as
specifying different mappings from state to props and from ReSwift
actions to actions. (See also section ‘Open Issues’ below.)
Rapid prototyping. You can easily use dummy props and actions so
that you get a working UI layer prototype. Without writing any of your
application’s business logic, you can implement your presentation
layer in such a way that it is very simple to replace the dummies
with real state and actions.
Installation
The easiest way to use this library is through Cocoapods or Carthage. For CocoaPods, add this to your Podfile:
pod 'ReRxSwift', '~> 2.0'
For Carthage, add this to your Cartfile:
github "svdo/ReRxSwift" ~> 2.0
Usage
This section assumes that there is a global variable store that contains
your app’s store, and that it’s type is Store<AppState>. You have a view
controller that manages a text field; the text field displays a value from
your state, and on editingDidEnd you trigger an action to store the text
field’s content back into your state. To use ReRxSwift for your view
controller MyViewController, you use the following steps.
Create an extension to your view controller to make it Connectable,
defining the Props and Actions that your view controller needs:
This is the protocol that your view controller has to conform to. It requires
you to add a connection property. It provides the props and actions that
you can use in your view controller. Normally, you declare the connection as
follows:
class MyViewController: Connectable {
let connection = Connection(
store: store,
mapStateToProps: mapStateToProps,
mapDispatchToActions: mapDispatchToActions
)
}
Refer to the Connection constructor documentation for more information.
props
This contains the Props object that you create using mapStateToProps. In
other words: it contains all data that your view controller uses, automatically
extracted from your application state. When using the bind methods in
Connection, you probably don’t need to use this props property directly.
actions
This contains the Actions object that you create using mapDispatchToActions.
In other words: it specifies which ReSwift action has to be dispatched when
calling the callbacks defined by your actions.
Connection
The Connection takes care of the mapping from you application state to your
view controller props, and of dispatching the mapped action when calling
functions in your view controller actions.
To create your Connection instance, you need to construct it with three
parameters:
store: Your application’s ReSwift store.
mapStateToProps: A function that takes values from your application’s
state and puts them in the view controller’s props object. This decouples
your application state from the view controller data.
mapDispatchToActions: A function that specifies which actions your view
controller can call, and for each of those which ReSwift action needs to be
dispatched.
connect()
Calling this method causes the connection to subscribe to the ReSwift store and
receive application state updates. Call this from your view controller’s
viewWillAppear or viewDidAppear method.
disconnect()
Calling this method causes the connection to unsubscribe from the ReSwift store.
Call this from your view controller’s viewWillDisappear or viewDidDisappear.
subscribe(keyPath, onNext)
Subscribe to an entry in your view controller’s props, meaning that the given
onNext handler will be called whenever that entry changes.
bind(keyPath, to, mapping)
This function binds an entry in your view controller’s props to a
RxSwift-enabled user interface element, so that every time your props change,
the user interface element is updated accordingly, automatically.
The function bind takes the following parameters:
keyPath: The (Swift 4) key path that points to the element in your props
that you want to bind to the user interface element.
to: The RxSwift reactive property wrapper, e.g. textField.rx.text or
progressView.rx.progress.
mapping: Most of the bind variants (but not all of them) allow you to
provide a mapping function. This mapping function is applied to the props
element at the specified keyPath. You can use this for example for type
conversions: your props contains a value as a Float, but the UI element
requires it to be a String. Specifying the mapping { String($0) } will
take care of that. SteppingUpViewController.swift contains an example
of a mapping function that maps a Float value to the selected index of
a segmented control.
Just for your understanding: there are several variants of the bind function.
They are all variants of this simplified code:
The folder Example contains the following examples:
SimpleTextField: Most basic use case of ReRxSwift, containing a text field that has its value bound, and an action.
SteppingUp: Use case with multiple bound values and actions, also showing how to transform values when binding them.
TableAndCollection: Shows how to use ReRxSwift in combination with RxSwift, RxCocoa and RxDataSources to have very simple table/collection views that automatically animate their updates.
FAQ
My props are not updated when the application state changes?
This happens when you forget to call connection.connect() in you view
controller’s viewWillAppear or viewDidAppear method. While you’re at it,
you may want to verify that you also call connection.disconnect() in
viewWillDisappear or viewDidDisappear.
I get compiler errors when calling connection.bind()?
When calling bind, you pass a key path to an element in your props object.
Because of the way ReRxSwift makes sure to only trigger when this element
actually changed, it compares its value with the previous one. This means
that the elements in your props object need to be Equatable. Simple types
of course are already Equatable, but especially when binding table view
items or collection view items, you need to make sure that the types are
Equatable.
Another common source of errors is when the type of the element in your props
does not exactly match the expected type by the user interface element. For
example, you bind to a stepper’s stepValue, which is a Double, but your
props contains a Float. In these cases you can pass a mapping function as
the third parameter to bind(_:to:mapping:) to cast the props element to
the expected type. See SteppingUpViewController.swift for examples.
I double-checked everything and I still get errors!
Please open a new issue on GitHub, as you may have run into a bug. (But please make sure everything inside your Props type is Equatable!)
ReRxSwift
RxSwift bindings for ReSwift
Table of Contents
Introduction
In case you don’t know them yet, these are two awesome frameworks:
Using those two frameworks you can make very nice app architectures. RxSwift allows you to bind application state to your UI, and ReSwift emits state updates in response to actions. We take it one step further though.
Similar to react-redux, ReRxSwift allows you to create view controllers that have
propsandactions. View controllers read all data they need from theirprops(instead of directly from the state), and they change data by invoking callbacks defined byactions(instead of directly dispatching ReSwift actions). This has some nice advantages:propsand from ReSwift actions toactions. (See also section ‘Open Issues’ below.)propsandactionsso that you get a working UI layer prototype. Without writing any of your application’s business logic, you can implement your presentation layer in such a way that it is very simple to replace the dummies with real state and actions.Installation
The easiest way to use this library is through Cocoapods or Carthage. For CocoaPods, add this to your
Podfile:For Carthage, add this to your
Cartfile:Usage
This section assumes that there is a global variable
storethat contains your app’s store, and that it’s type isStore<AppState>. You have a view controller that manages a text field; the text field displays a value from your state, and oneditingDidEndyou trigger an action to store the text field’s content back into your state. To use ReRxSwift for your view controllerMyViewController, you use the following steps.Create an extension to your view controller to make it
Connectable, defining thePropsandActionsthat your view controller needs:Define how your state is mapped to the above
Propstype:Define the actions that are dispatched:
Define the connection and hook it up:
Bind the text field’s text, using a Swift 4 key path to refer to the
textproperty ofProps:Call the action:
This is pretty much the
SimpleTextFieldViewControllerinside the sample app. That view controller comes with complete unit tests:SimpleTextFieldViewControllerSpec.API
Below is a high-level description of the most important components of the API. There is also full API documentation of ReRxSwift available.
Connectable
This is the protocol that your view controller has to conform to. It requires you to add a
connectionproperty. It provides thepropsandactionsthat you can use in your view controller. Normally, you declare theconnectionas follows:Refer to the
Connectionconstructor documentation for more information.props
This contains the
Propsobject that you create usingmapStateToProps. In other words: it contains all data that your view controller uses, automatically extracted from your application state. When using thebindmethods inConnection, you probably don’t need to use thispropsproperty directly.actions
This contains the
Actionsobject that you create usingmapDispatchToActions. In other words: it specifies which ReSwift action has to be dispatched when calling the callbacks defined by youractions.Connection
The
Connectiontakes care of the mapping from you application state to your view controllerprops, and of dispatching the mapped action when calling functions in your view controlleractions.Constructor(store, mapStateToProps, mapDispatchToActions)
To create your
Connectioninstance, you need to construct it with three parameters:propsobject. This decouples your application state from the view controller data.connect()
Calling this method causes the connection to subscribe to the ReSwift store and receive application state updates. Call this from your view controller’s
viewWillAppearorviewDidAppearmethod.disconnect()
Calling this method causes the connection to unsubscribe from the ReSwift store. Call this from your view controller’s
viewWillDisappearorviewDidDisappear.subscribe(keyPath, onNext)
Subscribe to an entry in your view controller’s
props, meaning that the givenonNexthandler will be called whenever that entry changes.bind(keyPath, to, mapping)
This function binds an entry in your view controller’s
propsto a RxSwift-enabled user interface element, so that every time yourpropschange, the user interface element is updated accordingly, automatically.The function
bindtakes the following parameters:propsthat you want to bind to the user interface element.textField.rx.textorprogressView.rx.progress.bindvariants (but not all of them) allow you to provide a mapping function. This mapping function is applied to thepropselement at the specifiedkeyPath. You can use this for example for type conversions: yourpropscontains a value as aFloat, but the UI element requires it to be aString. Specifying the mapping{ String($0) }will take care of that.SteppingUpViewController.swiftcontains an example of a mapping function that maps aFloatvalue to the selected index of a segmented control.Just for your understanding: there are several variants of the
bindfunction. They are all variants of this simplified code:Example App
The folder
Examplecontains the following examples:FAQ
My
propsare not updated when the application state changes?This happens when you forget to call
connection.connect()in you view controller’sviewWillAppearorviewDidAppearmethod. While you’re at it, you may want to verify that you also callconnection.disconnect()inviewWillDisappearorviewDidDisappear.I get compiler errors when calling
connection.bind()?When calling
bind, you pass a key path to an element in yourpropsobject. Because of the way ReRxSwift makes sure to only trigger when this element actually changed, it compares its value with the previous one. This means that the elements in yourpropsobject need to beEquatable. Simple types of course are alreadyEquatable, but especially when binding table view items or collection view items, you need to make sure that the types areEquatable.Another common source of errors is when the type of the element in your
propsdoes not exactly match the expected type by the user interface element. For example, you bind to a stepper’sstepValue, which is aDouble, but yourpropscontains aFloat. In these cases you can pass a mapping function as the third parameter tobind(_:to:mapping:)to cast thepropselement to the expected type. SeeSteppingUpViewController.swiftfor examples.I double-checked everything and I still get errors!
Please open a new issue on GitHub, as you may have run into a bug. (But please make sure everything inside your
Propstype isEquatable!)