Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
Thibault Wittemberg committed May 23, 2020
2 parents e54490a + 2de92c9 commit 31557fa
Show file tree
Hide file tree
Showing 19 changed files with 705 additions and 176 deletions.
1 change: 0 additions & 1 deletion .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ opt_in_rules:
- extension_access_modifier
- fallthrough
- file_header
- file_name
- first_where
- flatmap_over_map_reduce
- identical_operands
Expand Down
34 changes: 27 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,21 @@ class WatchedFlow: Flow {
}
```

### How to handle Deeplinks

From the AppDelegate you can reach the FlowCoordinator and call the `navigate(to:)` function when receiving a notification for instance.

The step passed to the function will then be passed to all the existing Flows so you can adapt the navigation.

```swift
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
// example of how DeepLink can be handled
self.coordinator.navigate(to: DemoStep.movieIsPicked(withId: 23452))
}
```

### How to adapt a Step before it triggers a navigation ?

A Flow has a `adapt(step:) -> Single<Step>` function that by default returns the step it has been given
Expand Down Expand Up @@ -271,17 +286,22 @@ Of course, it is the aim of a Coordinator. Inside a Flow we can present UIViewCo
For instance, from the WishlistFlow, we launch the SettingsFlow in a popup.

```swift
private func navigateToSettings() -> FlowContributors {
let settingsStepper = SettingsStepper()
let settingsFlow = SettingsFlow(withServices: self.services, andStepper: settingsStepper)
private func navigateToSettings() -> FlowContributors {
let settingsStepper = SettingsStepper()
let settingsFlow = SettingsFlow(withServices: self.services, andStepper: settingsStepper)

Flows.whenReady(flow1: settingsFlow) { [unowned self] (root: UISplitViewController) in
self.rootViewController.present(root, animated: true)
}
return .one(flowContributor: .contribute(withNextPresentable: settingsFlow, withNextStepper: settingsStepper))
Flows.use(settingsFlow, when: .ready) { [unowned self] root in
self.rootViewController.present(root, animated: true)
}

return .one(flowContributor: .contribute(withNextPresentable: settingsFlow, withNextStepper: settingsStepper))
}
```

The `Flows.use(when:)` takes an `ExecuteStrategy` as a second parameter. It has two possible values:
- .created: The completion block will be executed instantly
- .ready: The completion block will be executed once the sub flows (SettingsFlow in the example) have emitted a first step

For more complex cases, see the **DashboardFlow.swift** and the **SettingsFlow.swift** files in which we handle a UITabBarController and a UISplitViewController.

### How to bootstrap the RxFlow process
Expand Down
2 changes: 1 addition & 1 deletion RxFlow.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "RxFlow"
s.version = "2.8.0"
s.version = "2.9.0"
s.swift_version = '5.2.2'
s.summary = "RxFlow is a navigation framework for iOS applications, based on a Reactive Coordinator pattern."

Expand Down
20 changes: 18 additions & 2 deletions RxFlow.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
741801AC21CB31C200D25B4B /* FlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 741801AB21CB31C200D25B4B /* FlowCoordinator.swift */; };
74319EE8231C300A002294F3 /* FlowContributorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74319EE7231C300A002294F3 /* FlowContributorTests.swift */; };
74370EB023C65E20006CB8A6 /* FlowsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74370EAF23C65E20006CB8A6 /* FlowsTests.swift */; };
74855A472470544500334B8F /* DeprecatedFlows.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74855A462470544500334B8F /* DeprecatedFlows.swift */; };
74855A492470552B00334B8F /* DeprecatedFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74855A482470552B00334B8F /* DeprecatedFlowCoordinator.swift */; };
749B02E822066518001BEBBB /* RxBlocking.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 749B02E722066518001BEBBB /* RxBlocking.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
749B02EB22066538001BEBBB /* Reusable.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 749B02E922066537001BEBBB /* Reusable.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
749B02EC22066538001BEBBB /* RxTest.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 749B02EA22066538001BEBBB /* RxTest.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
Expand Down Expand Up @@ -97,6 +99,8 @@
741801AB21CB31C200D25B4B /* FlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowCoordinator.swift; sourceTree = "<group>"; };
74319EE7231C300A002294F3 /* FlowContributorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowContributorTests.swift; sourceTree = "<group>"; };
74370EAF23C65E20006CB8A6 /* FlowsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowsTests.swift; sourceTree = "<group>"; };
74855A462470544500334B8F /* DeprecatedFlows.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeprecatedFlows.swift; sourceTree = "<group>"; };
74855A482470552B00334B8F /* DeprecatedFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeprecatedFlowCoordinator.swift; sourceTree = "<group>"; };
749B02E722066518001BEBBB /* RxBlocking.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxBlocking.framework; path = Carthage/Build/iOS/RxBlocking.framework; sourceTree = "<group>"; };
749B02E922066537001BEBBB /* Reusable.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Reusable.framework; path = Carthage/Build/iOS/Reusable.framework; sourceTree = "<group>"; };
749B02EA22066538001BEBBB /* RxTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxTest.framework; path = Carthage/Build/iOS/RxTest.framework; sourceTree = "<group>"; };
Expand Down Expand Up @@ -203,6 +207,7 @@
1AF8548E1FF8328D00271B52 /* RxFlow */ = {
isa = PBXGroup;
children = (
74855A452470542000334B8F /* Deprecated */,
1AF8548F1FF8328D00271B52 /* RxFlow.h */,
1AF854901FF8328D00271B52 /* Info.plist */,
1A8FBE031FF8430F00389464 /* Extensions */,
Expand All @@ -217,6 +222,15 @@
path = RxFlow;
sourceTree = "<group>";
};
74855A452470542000334B8F /* Deprecated */ = {
isa = PBXGroup;
children = (
74855A462470544500334B8F /* DeprecatedFlows.swift */,
74855A482470552B00334B8F /* DeprecatedFlowCoordinator.swift */,
);
path = Deprecated;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXHeadersBuildPhase section */
Expand Down Expand Up @@ -369,7 +383,9 @@
1A8FBDF41FF8337800389464 /* Stepper.swift in Sources */,
1A8FBDF31FF8337800389464 /* Reactive+UIViewController.swift in Sources */,
1A8FBDFA1FF8337800389464 /* Synchronizable.swift in Sources */,
74855A472470544500334B8F /* DeprecatedFlows.swift in Sources */,
1A8FBDFB1FF8337800389464 /* Presentable.swift in Sources */,
74855A492470552B00334B8F /* DeprecatedFlowCoordinator.swift in Sources */,
1A8FBDF21FF8337800389464 /* UIViewController+Presentable.swift in Sources */,
1A8FBDF71FF8337800389464 /* Reactive+UIWindow.swift in Sources */,
741801AC21CB31C200D25B4B /* FlowCoordinator.swift in Sources */,
Expand Down Expand Up @@ -571,7 +587,7 @@
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 2.8.0;
MARKETING_VERSION = 2.9.0;
PRODUCT_BUNDLE_IDENTIFIER = io.warpfactor.RxFlow;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down Expand Up @@ -602,7 +618,7 @@
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 2.8.0;
MARKETING_VERSION = 2.9.0;
PRODUCT_BUNDLE_IDENTIFIER = io.warpfactor.RxFlow;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
PROVISIONING_PROFILE_SPECIFIER = "";
Expand Down
15 changes: 15 additions & 0 deletions RxFlow/Deprecated/DeprecatedFlowCoordinator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// DeprecatedFlowCoordinator.swift
// RxFlow
//
// Created by Thibault Wittemberg on 2020-05-16.
// Copyright © 2020 RxSwiftCommunity. All rights reserved.
//

#if canImport(UIKit)

/// typealias to allow compliance with older versions of RxFlow. Coordinator should be replaced by FlowCoordinator
@available(*, deprecated, message: "You should use FlowCoordinator")
public typealias Coordinator = FlowCoordinator

#endif
212 changes: 212 additions & 0 deletions RxFlow/Deprecated/DeprecatedFlows.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
//
// DeprecatedFlows.swift
// RxFlow
//
// Created by Thibault Wittemberg on 2020-05-16.
// Copyright © 2020 RxSwiftCommunity. All rights reserved.
//

#if canImport(UIKit)

import RxRelay
import RxSwift
import UIKit

@available(*, deprecated, message: "You should use Flows.use()")
public extension Flows {
/// Allow to be triggered only when Flows given as parameters are ready to be displayed.
/// Once it is the case, the block is executed
///
/// - Parameters:
/// - flows: Flow(s) to be observed
/// - block: block to execute whenever the Flows are ready to use
@available(*, deprecated, message: "You should use Flows.use()")
static func whenReady<RootType: UIViewController>(flows: [Flow],
block: @escaping ([RootType]) -> Void) {
let flowsReadinesses = flows.map { $0.rxFlowReady }
let roots = flows.compactMap { $0.root as? RootType }
guard roots.count == flows.count else {
fatalError("Type mismatch, Flows roots types do not match the types awaited in the block")
}

_ = Single.zip(flowsReadinesses) { _ in Void() }
.asDriver(onErrorJustReturn: Void())
.drive(onNext: { _ in
block(roots)
})
}

// swiftlint:disable function_parameter_count
/// Allow to be triggered only when Flows given as parameters are ready to be displayed.
/// Once it is the case, the block is executed
///
/// - Parameters:
/// - flow1: first Flow to be observed
/// - flow2: second Flow to be observed
/// - flow3: third Flow to be observed
/// - flow4: fourth Flow to be observed
/// - flow5: fifth Flow to be observed
/// - block: block to execute whenever the Flows are ready to use
@available(*, deprecated, message: "You should use Flows.use()")
static func whenReady<RootType1, RootType2, RootType3, RootType4, RootType5>(flow1: Flow,
flow2: Flow,
flow3: Flow,
flow4: Flow,
flow5: Flow,
block: @escaping (_ flow1Root: RootType1,
_ flow2Root: RootType2,
_ flow3Root: RootType3,
_ flow4Root: RootType4,
_ flow5Root: RootType5) -> Void)
where
RootType1: UIViewController,
RootType2: UIViewController,
RootType3: UIViewController,
RootType4: UIViewController,
RootType5: UIViewController {
guard
let root1 = flow1.root as? RootType1,
let root2 = flow2.root as? RootType2,
let root3 = flow3.root as? RootType3,
let root4 = flow4.root as? RootType4,
let root5 = flow5.root as? RootType5 else {
fatalError("Type mismatch, Flows roots types do not match the types awaited in the block")
}

_ = Single.zip(flow1.rxFlowReady,
flow2.rxFlowReady,
flow3.rxFlowReady,
flow4.rxFlowReady,
flow5.rxFlowReady) { _, _, _, _, _ in Void() }
.asDriver(onErrorJustReturn: Void())
.drive(onNext: { _ in
block(root1, root2, root3, root4, root5)
})
}

// swiftlint:enable function_parameter_count
/// Allow to be triggered only when Flows given as parameters are ready to be displayed.
/// Once it is the case, the block is executed
///
/// - Parameters:
/// - flow1: first Flow to be observed
/// - flow2: second Flow to be observed
/// - flow3: third Flow to be observed
/// - flow4: fourth Flow to be observed
/// - block: block to execute whenever the Flows are ready to use
@available(*, deprecated, message: "You should use Flows.use()")
static func whenReady<RootType1, RootType2, RootType3, RootType4>(flow1: Flow,
flow2: Flow,
flow3: Flow,
flow4: Flow,
block: @escaping (_ flow1Root: RootType1,
_ flow2Root: RootType2,
_ flow3Root: RootType3,
_ flow4Root: RootType4) -> Void)
where
RootType1: UIViewController,
RootType2: UIViewController,
RootType3: UIViewController,
RootType4: UIViewController {
guard
let root1 = flow1.root as? RootType1,
let root2 = flow2.root as? RootType2,
let root3 = flow3.root as? RootType3,
let root4 = flow4.root as? RootType4 else {
fatalError("Type mismatch, Flows roots types do not match the types awaited in the block")
}

_ = Single.zip(flow1.rxFlowReady,
flow2.rxFlowReady,
flow3.rxFlowReady,
flow4.rxFlowReady) { _, _, _, _ in Void()
}
.asDriver(onErrorJustReturn: Void())
.drive(onNext: { _ in
block(root1, root2, root3, root4)
})
}

/// Allow to be triggered only when Flows given as parameters are ready to be displayed.
/// Once it is the case, the block is executed
///
/// - Parameters:
/// - flow1: first Flow to be observed
/// - flow2: second Flow to be observed
/// - flow3: third Flow to be observed
/// - block: block to execute whenever the Flows are ready to use
@available(*, deprecated, message: "You should use Flows.use()")
static func whenReady<RootType1, RootType2, RootType3>(flow1: Flow,
flow2: Flow,
flow3: Flow,
block: @escaping (_ flow1Root: RootType1,
_ flow2Root: RootType2,
_ flow3Root: RootType3) -> Void)
where
RootType1: UIViewController,
RootType2: UIViewController,
RootType3: UIViewController {
guard
let root1 = flow1.root as? RootType1,
let root2 = flow2.root as? RootType2,
let root3 = flow3.root as? RootType3 else {
fatalError("Type mismatch, Flows roots types do not match the types awaited in the block")
}

_ = Single.zip(flow1.rxFlowReady,
flow2.rxFlowReady,
flow3.rxFlowReady) { _, _, _ in Void() }
.asDriver(onErrorJustReturn: Void())
.drive(onNext: { _ in
block(root1, root2, root3)
})
}

/// Allow to be triggered only when Flows given as parameters are ready to be displayed.
/// Once it is the case, the block is executed
///
/// - Parameters:
/// - flow1: first Flow to be observed
/// - flow2: second Flow to be observed
/// - block: block to execute whenever the Flows are ready to use
@available(*, deprecated, message: "You should use Flows.use()")
static func whenReady<RootType1: UIViewController, RootType2: UIViewController>(flow1: Flow,
flow2: Flow,
block: @escaping (_ flow1Root: RootType1,
_ flow2Root: RootType2) -> Void) {
guard let root1 = flow1.root as? RootType1,
let root2 = flow2.root as? RootType2 else {
fatalError("Type mismatch, Flows root types do not match the types awaited in the block")
}

_ = Single.zip(flow1.rxFlowReady,
flow2.rxFlowReady) { _, _ in Void() }
.asDriver(onErrorJustReturn: Void())
.drive(onNext: { _ in
block(root1, root2)
})
}

/// Allow to be triggered only when Flow given as parameters is ready to be displayed.
/// Once it is the case, the block is executed
///
/// - Parameters:
/// - flow1: Flow to be observed
/// - block: block to execute whenever the Flow is ready to use
@available(*, deprecated, message: "You should use Flows.use()")
static func whenReady<RootType: UIViewController>(flow1: Flow,
block: @escaping (_ flowRoot1: RootType) -> Void) {
guard let root = flow1.root as? RootType else {
fatalError("Type mismatch, Flow root type does not match the type awaited in the block")
}

_ = flow1
.rxFlowReady
.asDriver(onErrorJustReturn: true)
.drive(onNext: { _ in
block(root)
})
}
}

#endif
Loading

0 comments on commit 31557fa

Please sign in to comment.