Swiftpack.co - Package - xflagstudio/RxController

RxController

Introduction

RxController is a library for the development with MVVM-C based on RxFlow and RxSwift. If you are not familiar with them, please learn these frameworks at first:

  • RxSwift (https://github.com/ReactiveX/RxSwift)
  • RxCocoa (https://github.com/ReactiveX/RxSwift)
  • RxFlow (https://github.com/RxSwiftCommunity/RxFlow)

RxController provides the the following basic view controller and view model classes.

  • RxViewController
  • RxViewModel

These classes make it easy to transfer data among the flows, the parent view models and the child view models.

Recommended guideline

We recommend to develop a MMVM-C based on RxController, RxFlow and RxSwift in this guideline. It better to read the documentation of RxController, RxFlow and RxSwift at first, if you are not familiar with them.

Documentation

RxController is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'RxController'

Example

The example app helps you to understand how to use RxController. To run the example project, clone the repo, and run pod install from the Example directory first.

Generic class of View Controller

RxController provides a generic classes RxViewController. It avoids using an Optional or an Implicit Unwrapping Option type for the view model property in the view controller class.

In the demo app, we define the view model class by extending the RxViewModel class, and the view controller class by extending the RxViewController generic class.

// View model class
class InfoViewModel: RxViewModel {

}

// View controller class
class InfoViewController: RxViewController<InfoViewModel> {

}

Then, we can initialize the InfoViewController with a safe way as the following.

func navigate(to step: Step) -> FlowContributors {
    guard let appStep = step as? AppStep else {
        return .none
    }
    switch appStep {
    case .start:
        let infoViewController = InfoViewController(viewModel: InfoViewModel())
        navigationController.pushViewController(infoViewController, animated: false)
        return .viewController(infoViewController)
    }
}

Exchange data among parent and child view models

In a standard MVVM-C architecture using RxFlow, view models exchange data via a flow class using the steps.accept() method. With RxChildViewModel, we can exchange data among parent and child view models without the flow class.

Use the following method to add a child view controller to the root view or a customized view of its parent controller.

/**
 Add a child view controller to the root view of the parent view controller.

 @param childController: a child view controller.
 */
override open func addChild(_ childController: UIViewController)

/**
 Add a child view controller to the a container view of the parent view controller.
 The edges of the child view controller is same as the container view by default.
 
 @param childController: a child view controller.
 @param containerView: a container view of childController.
 */
open func addChild(_ childController: UIViewController, to containerView: UIView)

To transfer data among view models, we define some events with a struct in the parent view model.

struct InfoEvent {
    static let name = RxControllerEvent.identifier()
    static let number = RxControllerEvent.identifier()
}

Platform

As shown in the graph, the events can only be transfered among a parent view model and its first generation child view models. For example, the InfoEvent we defined above, is enabled among InfoViewModel, NameViewModel and NumberViewModel.

Send a event from the parent view model (InfoViewModel).

events.accept(InfoEvent.name.event("Alice"))

Send a event from the child view model (NameViewModel and NumberViewModel).

parentEvents.accept(event: InfoEvent.name.event("Alice"))

Receive a event in the parent view model (InfoViewModel).

var name: Observable<String?> {
    return events.value(of: InfoEvent.name)
}

Receive a event in the child view model (NameViewModel and NumberViewModel).

var name: Observable<String?> {
    return parentEvents.value(of: InfoEvent.name)
}

Pay attention to that subscribing the RxControllerEvent in the init method of the view model is not effective. It necessary to subscribe or bind the RxControllerEvent in the prepareForParentEvents methods.

override func prepareForParentEvents() {
    // Subscribe an event.
    parentEvents.unwrappedValue(of: ParentEvent.sample, type: EventData.self).subscribe(onNext: { 
        // ...
    }.disposed(by: disposeBag))

    // Bind an event or a parent event to a relay directly.
    bindParentEvents(to: data, with: ParentEvent.sample)

    // Bind an observable type to an event or a parent event directly.
    bindToEvent(from: data, with: Event.sample)

}

Event router in the view model

In the graph above, if an event needs to be transfered from InfoViewModel to FirstNameViewModel, the mid view model NameViewModel should be used as a router to forward data. To simply the data forwarding in the router view model, the forward methods are provided in the RxViewModel.

// Forward a parent event to an event
func forward(parentEvent: ,toEvent:)

// Forward a parent event to an event with a `flatMapLatest` closure
func forward(parentEvent: ,toEvent: ,flatMapLatest:)

// Forward an event to a parent event
func forward(toEvent: ,parentEvent:)

// Forward an event to a parent event with a `flatMapLatest` closure
func forward(toEvent: , parentEvent: ,flatMapLatest:)

Send a step to the flow from a child view model

In a general way, the method steps.accpet() of RxFlow cannot be revoked from a child view model, because we didn't return the instances of the child view controller and child view model in the navigate(to) method of a flow.

With RxController, it is able to send a step to the flow from a child view model directly.

steps.accept(DemoStep.stepname)

RxTree

RxController provides a command line tool rxtree to print the relationship among flows and view controllers, just like using the tree command.

➜  ./rxtree MainFlow
MainFlow
├── ProjectFlow
│   ├── RequestFlow
│   │   ├── AddProjectViewController
│   │   ├── RequestViewController
│   │   ├── ResultViewController
│   │   ├── SaveToProjectViewController
│   ├── ProjectIntroductionViewController
│   ├── ProjectNameViewController
│   ├── ProjectViewController
│   ├── ProjectsViewController
├── RequestFlow
│   ├── AddProjectViewController
│   ├── RequestViewController
│   ├── ResultViewController
│   ├── SaveToProjectViewController
├── SettingsFlow
│   ├── IPAddressViewController
│   ├── PingViewController
│   ├── SettingsViewController
│   ├── WhoisViewController
├── AddProjectViewController

Install RxTree with CocoaPods

rxtree relays on the design of RxController. Once RxController updated, the old version of rxtree may be noneffective. For this reason, it is recommend to be installed with post_install of CocoaPods.

post_install do |installer|
  system("bash #{Pathname(installer.sandbox.root)}/RxController/rxtree/build_for_xcode.sh")
end

Once pod install or pod update is executed, the corresponding version of rxtree will be installed at the same time.

Use RxTree

The executed file rxtree will be copied to the root directory of the project. A root node which can be a subclass of Flow or a subclass of RxViewController must be selected as the root of the tree.

./rxtree MainFlow

To prevent recustion calling, the default max levels of rxtree is 10. It means that only 10 levels of flows and view controllers will be listed by default. To change the value of max levels, use the paramter maxLevels.

./rxtree MainFlow --maxLevels 5

Author

lm2343635, lm2343635@126.com

License

RxController is available under the MIT license. See the LICENSE file for more info.

Github

link
Stars: 13

Used By

Total: 0

Releases

- 2020-02-15 13:19:46

  • Add Cacheable to RxControllerEvent #20

- 2020-02-11 09:12:52

  • Add functions of removeCachedEvent #19

- 2019-12-05 09:20:56

  • Add maxLevels parameter for rxtree
➜  Example git:(develop/rxtree) ✗ ./rxtree AppFlow --maxLevels 4
AppFlow
├── MainFlow
│   ├── ChildFlow
│   │   ├── InfoViewController
│   │   │   ├── NumberViewController
│   ├── ProfileFlow
│   │   ├── FriendsFlow
│   │   │   ├── ProfileFlow
│   │   │   ├── FriendsViewController
│   │   ├── ProfileViewController

- 2019-11-26 07:53:58

  • New features for rxtree
    • Support to list child view controllers.
    • Fix the duplicated controllers and flows.
    • Scan subclass of RxViewController automatically.

- 2019-11-21 09:48:26

  • Add rxtree command.
  • To build and use rxtree automatically while running pod install, add the following post_install script into your Podfile
post_install do |installer|
    system("bash #{Pathname(installer.sandbox.root)}/RxController/rxtree/build_for_xcode.sh")
end
  • To use rxtree, run the following command in the root directory which contains the Xcode project file.
./rxtree [FlowName]

- 2019-11-20 06:42:44

  • Remove .navigationController().

- 2019-10-16 02:17:13

Add support for Swift Package Manager #9

- 2019-09-24 02:29:06

  • Step event should not be cached #8

- 2019-08-07 02:02:46

  • Add RxControllerEventBinder.
  • Add RxControllerEventRouter.

- 2019-08-05 02:41:08

  • Bind RxController event to relay directly.

- 2019-07-19 08:51:04

  • Fix the reentrancy anomaly warning.
⚠️ Reentrancy anomaly was detected.
  > Debugging: To debug this issue you can set a breakpoint in /xxxx/Pods/RxSwift/RxSwift/Rx.swift:96 and observe the call stack.
  > Problem: This behavior is breaking the observable sequence grammar. `next (error | completed)?`
    This behavior breaks the grammar because there is overlapping between sequence events.
    Observable sequence is trying to send an event before sending of previous event has finished.
  > Interpretation: This could mean that there is some kind of unexpected cyclic dependency in your code,
    or that the system is not behaving in the expected way.
  > Remedy: If this is the expected behavior this message can be suppressed by adding `.observeOn(MainScheduler.asyncInstance)`
    or by enqueuing sequence events in some other way.

- 2019-06-24 02:38:19

  • Add .navigationController wrapper method.

- 2019-06-21 05:31:56

  • Republish events if child events was set.

- 2019-06-21 02:07:45

  • Enable to access the parent root ViewModel's steps from ChildModel.

  • Remove acceptStepsEvent method and use steps from child view model directly.

  • Rename addChildModel and addChildModels

    • addChildModel -> addChild
    • addChildModels -> addChildren

- 2019-06-20 09:48:30

  • Use BehaviorRelay for events.
  • Remove lifecycle for view model.

- 2019-06-19 06:01:51

  • Unified addChild method

- 2019-06-18 09:40:21

  • Rename addChild method to addRxChild.
  • Remove removeChilds method.

- 2019-06-17 08:53:35

  • Assign data type of event.
  • Lifecycle for view model.

| View Model | View Controller | | ----- | ---- | | func controllerDidLoad() | func viewDidLoad() | | func controllerDidAppear() | func viewDidAppear(_ animated: Bool) | | func controllerDidDisappear() | func viewDidDisappear(_ animated: Bool) | | func controllerWillAppear() | func viewWillAppear(_ animated: Bool) | | func controllerWillDisappear() | func viewWillDisappear(_ animated: Bool) |

- 2019-06-04 02:23:36

  • Combine RxViewController and RxChildViewController

- 2019-05-27 08:42:35

  • Send a step to Flow from a child view model directly.
acceptStepsEvent(DemoStep.stepname)

- 2019-05-21 10:06:51

  • RxSwift 5 compatible.

- 2019-05-14 03:25:28

Add a child view controller to a customized view of its parent controller.

// add a child view controller to a customized view of its parent controller.
func addChild<ViewModel: RxChildViewModel>(_ childController: RxChildViewController<ViewModel>, to view: UIView, completion: ((UIView) -> Void)? = nil)

- 2019-04-18 03:34:26

Remove RxControllerEventValue and RxControllerEventType Use RxControllerEvent.identifier() to create a event identifier instead from this release.

struct InfoEvent {
    static let name = RxControllerEvent.identifier()
    static let number = RxControllerEvent.identifier()
}

- 2019-04-17 07:18:40