Binding - Data binding framework (view model binding on MVVM) written using propertyWrapper and resultBuilder

Overview

Binding

Data binding framework (view model binding on MVVM) written using @propertyWrapper and @resultBuilder

Requirement

Swift 5.1+, RxSwift (link)

Usage

Property

The property wrapper used for observable property in view model that can be binded to view property by using RxCocoa. There are 3 types of wrapper (2 for observable value, and 1 for observable action).

Bindable

One-way binding type of property with Driver as its projectedValue doc, and can use any type for its property type (wrappedValue).

final class ViewModel {
  @Bindable private(set) var name: String = ""
  @Bindable private(set) var description: String = ""
  
  func change() {
      name = "haha"
      description = "description"
  }
}

For the usage in View we could bind it by using the projected value

disposeBag.insert(
  vm.$name.drive(nameLabel.rx.text),
  vm.$description.drive(descLabel.rx.text)
)

Mutable

Two-way binding type of property with BehaviorRelay as its projectedValue, also same as Bindable it can use any type for its property type.

final class ViewModel {
  @Mutable var typedText: String = ""
  @Mutable var selectedTarget: Target = .vidioAdmin
}

For usage in View we cound bind it same as Bindable by using the projected value, but since its type is BehaviorRelay, it can also accept value from view.

disposeBag.insert(
  vm.$typedText.asDriver().drive(textInput.rx.text),
  textInput.rx.text.subscribe(onNext: { vm.$typedText.accept($0) /* or vm.typedText = $0 */ })
)

ViewAction

One parameter observable function, use to trigger view action from view model. Example of action would be show alert, open view controller, dismiss, etc. Note that the wrapped value type has to be a function with single parameter and Void return type and it will have Signal as the projected value.

final class ViewModel {
  @ViewAction var alert: (Message) -> Void
  
  func change() {
      alert(Message("Changed!"))
  }
}

To bind it in view,

disposeBag.insert(
  vm.$alert.emit(onNext: { [weak self] in self?.showAlert($0.textMessage) })
)
ViewAction without argument

Since ViewAction needs the wrapped value to be single argument function, it will be awkward to use Void as the parameter type. For this, we could use another type of ViewAction, ViewAction.NoParam. It's the same as ViewAction, with exception of wrapped value type has to be no argument function () -> Void.

final class ViewModel {
  @ViewAction.NoParam var dismiss:() -> Void
}

BindingContext

When using RxSwift to bind view and property, the subscription needs to be dispose at some point, this usually be done after the view for the binding has been disposed. To implement this, usually we use DisposeBag and add the subscriptions to it to let it auto dispose all the subscription when the DisposeBag disposed by the view.

// example
let disposeBag = DisposeBag()
view.rx.text.subscribe(onNext: { /*...*/ }).disposed(by: disposeBag)
// or when there are multiple subscription we use
disposeBag.insert(
  textObservable.subscribe(),
  nameObservable.subscribe()
)

When using binding, a protocol called BindingContext is introduced to provide a context where the binding should be done. When implementing this protocol, a property disposeBag need to be implemented for it will be used to dispose all subscriptions added inside the context when de-inited. binding(@BindingDisposables disposables: () -> Disposable) in BindingContext can be used as the scope for the subscriptions. For any subscriptions done in this function builder, it will be inserted into the disposeBag.

Note: @_functionBuilder is used to implement this behavior proposal doc more learning (apparently, it's been changed to @functionBuilder in the newer version)

final class ViewController: BindingContext {
  ...
  let disposeBag = DisposeBag() 
  override func viewDidLoad() {
    super.viewDidLoad()
    ...
    binding {
      viewModel.$text.drive(textLabel.rx.text)  // notice we don't add comma here, since it is not needed when using function builder
      viewModel.$description.drive(descLabel.rx.text)
      viewModel.$alert.emit(onNext: { [weak self] in self?.alert($0) })
    }
  }
}

Binding Operators

To simplify the binding, custom operators added to this library. There are 2 binding operators that can be use to bind the view with view model.

One-way Binding Operator

To handle one-way binding, operator => can be used with left-hand operand to be Driver or Signal(for ViewAction binding).

The right-hand operand for both Driver and Signal can be:

  • ObserverType with value type both optional or not, and it can also be function with one argument.
  • function with one argument (ValueType) -> Void.
binding {
  viewModel.$text => textLabel.rx.text // Driver with Binder as receiver
  viewModel.$description => { print("description: \($0)") } // Driver with function as receiver
  viewModel.$alert => { [weak self] in self?.alert($0) } // Signal with function as receiver
}

Two-way Binding Operator

To handle two-way binding, operator <=> can be used with left-hand operand to be BehaviorRelay and ControlPropertyType as the right-hand operand.

binding {
  // notice in this example text control property is not being used
  // instead it is using custom control property with non optional value
  // since, <=> cannot accept ControlPropertyType with element optional
  // for optional type a new operator will introduced
  viewModel.$inputText <=> textField.rx.nonNullText
}

Optional Operator

This operator is especially used for ControlProperty with default value. It has to be done this way because of how UIKit was implemented in the past, and RxCocoa has to adapt to it (e.g. text property in text field has String? type).

The operator for this case is re-using the same operator for Nil-coalescing (??) in swift.

binding {
  viewModel.$inputText <=> textField.rx.text ?? "default value"
}
You might also like...
Dynamic and type-safe framework for building linear and non-linear flows.

FlowKit FlowKit is a dynamic flow framework capable of building a flow, based on conditions and ordered according to a logic of next steps. By using F

Cocoa framework and Obj-C dynamism bindings for ReactiveSwift.
Cocoa framework and Obj-C dynamism bindings for ReactiveSwift.

Reactive extensions to Cocoa frameworks, built on top of ReactiveSwift. ⚠️ Looking for the Objective-C API? 🎉 Migrating from RAC 4.x? 🚄 Release Road

RxSwift reactive wrapper for view gestures
RxSwift reactive wrapper for view gestures

RxGesture Usage To run the example project, clone the repo, in the Example folder open RxGesture.xcworkspace. You might need to run pod install from t

Realm RxSwift - This application was written in order to use Realm, RxSwift frameworks in real example

Realm_RxSwift This simple app was written to introduce basic operations of some

Aftermath is a stateless message-driven micro-framework in Swift
Aftermath is a stateless message-driven micro-framework in Swift

Aftermath is a stateless message-driven micro-framework in Swift, which is based on the concept of the unidirectional data flow architecture.

An observables framework for Swift
An observables framework for Swift

🐌 snail A lightweight observables framework, also available in Kotlin Installation Carthage You can install Carthage with Homebrew using the followin

RxReduce is a lightweight framework that ease the implementation of a state container pattern in a Reactive Programming compliant way.
RxReduce is a lightweight framework that ease the implementation of a state container pattern in a Reactive Programming compliant way.

About Architecture concerns RxReduce Installation The key principles How to use RxReduce Tools and dependencies Travis CI Frameworks Platform Licence

A Swift framework for reactive programming.

CwlSignal An implementation of reactive programming. For details, see the article on Cocoa with Love, CwlSignal, a library for reactive programming. N

Open source implementation of Apple's Combine framework for processing values over time.
Open source implementation of Apple's Combine framework for processing values over time.

OpenCombine Open-source implementation of Apple's Combine framework for processing values over time. The main goal of this project is to provide a com

Owner
Sugeng Wibowo
Sugeng Wibowo
XTerminalUI - xterm.js binding to Apple user interface frameworks

XTerminalUI xterm.js binding to AppleUI with WebKit. Preview Usage We recommend

Lakr Aream 16 Jul 15, 2022
MVVM + FLUX iOS Instagram client in Swift, eliminates Massive View Controller in unidirectional event/state flow manner

CZInstagram MVVM + FLUX iOS Instagram client in Swift, eliminates Massive View Controller in unidirectional event/state flow manner. Unidirectional Da

Cheng Zhang 56 Nov 1, 2022
This is the demo of MVVM-C structure with dependency injection using RxSwift.

MVVMC-Demo Demo defination This is the demo project, I have integrated two APIs for MovieDB APIS (https://www.themoviedb.org/). One for the listing of

Anshul Shah 61 Dec 6, 2022
This Repository holds learning data on Combine Framework

Combine Framework List of Topics Welcome, every section in this repo contains a collection of exercises demonstrating combine's utilization as well as

Julio Ismael Robles 2 Mar 17, 2022
Core Data with ReactiveCocoa

ReactiveCoreData ReactiveCoreData (RCD) is an attempt to bring Core Data into the ReactiveCocoa (RAC) world. Currently has several files with the sour

Jacob Gorban 258 Aug 6, 2022
Unidirectional Data Flow in Swift - Inspired by Redux

ReSwift Supported Swift Versions: Swift 4.2, 5.x For Swift 3.2 or 4.0 Support use Release 5.0.0 or earlier. For Swift 2.2 Support use Release 2.0.0 or

null 7.3k Dec 25, 2022
RxSwift extensions for Core Data

RxCoreData Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements Xcode 9.0 Swift 4.0

RxSwift Community 164 Oct 14, 2022
🔄 Unidirectional data flow in Swift.

Reactor Reactor is a framework for making more reactive applications inspired by Elm, Redux, and recent work on ReSwift. It's small and simple (just o

Reactor 175 Jul 9, 2022
Unidirectional Data Flow in Swift - Inspired by Redux

ReSwift Supported Swift Versions: Swift 4.2, 5.x For Swift 3.2 or 4.0 Support use Release 5.0.0 or earlier. For Swift 2.2 Support use Release 2.0.0 or

null 7.3k Jan 3, 2023
A super simple library for state management with unidirectional data flow.

OneWay ?? OneWay is still experimental. As such, expect things to break and change in the coming months. OneWay is a super simple library for state ma

SeungYeop Yeom 41 Dec 20, 2022