Skip to content

Commit 31557fa

Browse files
author
Thibault Wittemberg
committed
Merge branch 'develop'
2 parents e54490a + 2de92c9 commit 31557fa

File tree

19 files changed

+705
-176
lines changed

19 files changed

+705
-176
lines changed

.swiftlint.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ opt_in_rules:
2626
- extension_access_modifier
2727
- fallthrough
2828
- file_header
29-
- file_name
3029
- first_where
3130
- flatmap_over_map_reduce
3231
- identical_operands

README.md

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,21 @@ class WatchedFlow: Flow {
205205
}
206206
```
207207

208+
### How to handle Deeplinks
209+
210+
From the AppDelegate you can reach the FlowCoordinator and call the `navigate(to:)` function when receiving a notification for instance.
211+
212+
The step passed to the function will then be passed to all the existing Flows so you can adapt the navigation.
213+
214+
```swift
215+
func userNotificationCenter(_ center: UNUserNotificationCenter,
216+
didReceive response: UNNotificationResponse,
217+
withCompletionHandler completionHandler: @escaping () -> Void) {
218+
// example of how DeepLink can be handled
219+
self.coordinator.navigate(to: DemoStep.movieIsPicked(withId: 23452))
220+
}
221+
```
222+
208223
### How to adapt a Step before it triggers a navigation ?
209224

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

273288
```swift
274-
private func navigateToSettings() -> FlowContributors {
275-
let settingsStepper = SettingsStepper()
276-
let settingsFlow = SettingsFlow(withServices: self.services, andStepper: settingsStepper)
289+
private func navigateToSettings() -> FlowContributors {
290+
let settingsStepper = SettingsStepper()
291+
let settingsFlow = SettingsFlow(withServices: self.services, andStepper: settingsStepper)
277292

278-
Flows.whenReady(flow1: settingsFlow) { [unowned self] (root: UISplitViewController) in
279-
self.rootViewController.present(root, animated: true)
280-
}
281-
return .one(flowContributor: .contribute(withNextPresentable: settingsFlow, withNextStepper: settingsStepper))
293+
Flows.use(settingsFlow, when: .ready) { [unowned self] root in
294+
self.rootViewController.present(root, animated: true)
295+
}
296+
297+
return .one(flowContributor: .contribute(withNextPresentable: settingsFlow, withNextStepper: settingsStepper))
282298
}
283299
```
284300

301+
The `Flows.use(when:)` takes an `ExecuteStrategy` as a second parameter. It has two possible values:
302+
- .created: The completion block will be executed instantly
303+
- .ready: The completion block will be executed once the sub flows (SettingsFlow in the example) have emitted a first step
304+
285305
For more complex cases, see the **DashboardFlow.swift** and the **SettingsFlow.swift** files in which we handle a UITabBarController and a UISplitViewController.
286306

287307
### How to bootstrap the RxFlow process

RxFlow.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = "RxFlow"
3-
s.version = "2.8.0"
3+
s.version = "2.9.0"
44
s.swift_version = '5.2.2'
55
s.summary = "RxFlow is a navigation framework for iOS applications, based on a Reactive Coordinator pattern."
66

RxFlow.xcodeproj/project.pbxproj

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
741801AC21CB31C200D25B4B /* FlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 741801AB21CB31C200D25B4B /* FlowCoordinator.swift */; };
2929
74319EE8231C300A002294F3 /* FlowContributorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74319EE7231C300A002294F3 /* FlowContributorTests.swift */; };
3030
74370EB023C65E20006CB8A6 /* FlowsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74370EAF23C65E20006CB8A6 /* FlowsTests.swift */; };
31+
74855A472470544500334B8F /* DeprecatedFlows.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74855A462470544500334B8F /* DeprecatedFlows.swift */; };
32+
74855A492470552B00334B8F /* DeprecatedFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74855A482470552B00334B8F /* DeprecatedFlowCoordinator.swift */; };
3133
749B02E822066518001BEBBB /* RxBlocking.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 749B02E722066518001BEBBB /* RxBlocking.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
3234
749B02EB22066538001BEBBB /* Reusable.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 749B02E922066537001BEBBB /* Reusable.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
3335
749B02EC22066538001BEBBB /* RxTest.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 749B02EA22066538001BEBBB /* RxTest.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -97,6 +99,8 @@
9799
741801AB21CB31C200D25B4B /* FlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowCoordinator.swift; sourceTree = "<group>"; };
98100
74319EE7231C300A002294F3 /* FlowContributorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowContributorTests.swift; sourceTree = "<group>"; };
99101
74370EAF23C65E20006CB8A6 /* FlowsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowsTests.swift; sourceTree = "<group>"; };
102+
74855A462470544500334B8F /* DeprecatedFlows.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeprecatedFlows.swift; sourceTree = "<group>"; };
103+
74855A482470552B00334B8F /* DeprecatedFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeprecatedFlowCoordinator.swift; sourceTree = "<group>"; };
100104
749B02E722066518001BEBBB /* RxBlocking.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxBlocking.framework; path = Carthage/Build/iOS/RxBlocking.framework; sourceTree = "<group>"; };
101105
749B02E922066537001BEBBB /* Reusable.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Reusable.framework; path = Carthage/Build/iOS/Reusable.framework; sourceTree = "<group>"; };
102106
749B02EA22066538001BEBBB /* RxTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxTest.framework; path = Carthage/Build/iOS/RxTest.framework; sourceTree = "<group>"; };
@@ -203,6 +207,7 @@
203207
1AF8548E1FF8328D00271B52 /* RxFlow */ = {
204208
isa = PBXGroup;
205209
children = (
210+
74855A452470542000334B8F /* Deprecated */,
206211
1AF8548F1FF8328D00271B52 /* RxFlow.h */,
207212
1AF854901FF8328D00271B52 /* Info.plist */,
208213
1A8FBE031FF8430F00389464 /* Extensions */,
@@ -217,6 +222,15 @@
217222
path = RxFlow;
218223
sourceTree = "<group>";
219224
};
225+
74855A452470542000334B8F /* Deprecated */ = {
226+
isa = PBXGroup;
227+
children = (
228+
74855A462470544500334B8F /* DeprecatedFlows.swift */,
229+
74855A482470552B00334B8F /* DeprecatedFlowCoordinator.swift */,
230+
);
231+
path = Deprecated;
232+
sourceTree = "<group>";
233+
};
220234
/* End PBXGroup section */
221235

222236
/* Begin PBXHeadersBuildPhase section */
@@ -369,7 +383,9 @@
369383
1A8FBDF41FF8337800389464 /* Stepper.swift in Sources */,
370384
1A8FBDF31FF8337800389464 /* Reactive+UIViewController.swift in Sources */,
371385
1A8FBDFA1FF8337800389464 /* Synchronizable.swift in Sources */,
386+
74855A472470544500334B8F /* DeprecatedFlows.swift in Sources */,
372387
1A8FBDFB1FF8337800389464 /* Presentable.swift in Sources */,
388+
74855A492470552B00334B8F /* DeprecatedFlowCoordinator.swift in Sources */,
373389
1A8FBDF21FF8337800389464 /* UIViewController+Presentable.swift in Sources */,
374390
1A8FBDF71FF8337800389464 /* Reactive+UIWindow.swift in Sources */,
375391
741801AC21CB31C200D25B4B /* FlowCoordinator.swift in Sources */,
@@ -571,7 +587,7 @@
571587
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
572588
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
573589
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
574-
MARKETING_VERSION = 2.8.0;
590+
MARKETING_VERSION = 2.9.0;
575591
PRODUCT_BUNDLE_IDENTIFIER = io.warpfactor.RxFlow;
576592
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
577593
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -602,7 +618,7 @@
602618
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
603619
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
604620
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
605-
MARKETING_VERSION = 2.8.0;
621+
MARKETING_VERSION = 2.9.0;
606622
PRODUCT_BUNDLE_IDENTIFIER = io.warpfactor.RxFlow;
607623
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
608624
PROVISIONING_PROFILE_SPECIFIER = "";
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//
2+
// DeprecatedFlowCoordinator.swift
3+
// RxFlow
4+
//
5+
// Created by Thibault Wittemberg on 2020-05-16.
6+
// Copyright © 2020 RxSwiftCommunity. All rights reserved.
7+
//
8+
9+
#if canImport(UIKit)
10+
11+
/// typealias to allow compliance with older versions of RxFlow. Coordinator should be replaced by FlowCoordinator
12+
@available(*, deprecated, message: "You should use FlowCoordinator")
13+
public typealias Coordinator = FlowCoordinator
14+
15+
#endif
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
//
2+
// DeprecatedFlows.swift
3+
// RxFlow
4+
//
5+
// Created by Thibault Wittemberg on 2020-05-16.
6+
// Copyright © 2020 RxSwiftCommunity. All rights reserved.
7+
//
8+
9+
#if canImport(UIKit)
10+
11+
import RxRelay
12+
import RxSwift
13+
import UIKit
14+
15+
@available(*, deprecated, message: "You should use Flows.use()")
16+
public extension Flows {
17+
/// Allow to be triggered only when Flows given as parameters are ready to be displayed.
18+
/// Once it is the case, the block is executed
19+
///
20+
/// - Parameters:
21+
/// - flows: Flow(s) to be observed
22+
/// - block: block to execute whenever the Flows are ready to use
23+
@available(*, deprecated, message: "You should use Flows.use()")
24+
static func whenReady<RootType: UIViewController>(flows: [Flow],
25+
block: @escaping ([RootType]) -> Void) {
26+
let flowsReadinesses = flows.map { $0.rxFlowReady }
27+
let roots = flows.compactMap { $0.root as? RootType }
28+
guard roots.count == flows.count else {
29+
fatalError("Type mismatch, Flows roots types do not match the types awaited in the block")
30+
}
31+
32+
_ = Single.zip(flowsReadinesses) { _ in Void() }
33+
.asDriver(onErrorJustReturn: Void())
34+
.drive(onNext: { _ in
35+
block(roots)
36+
})
37+
}
38+
39+
// swiftlint:disable function_parameter_count
40+
/// Allow to be triggered only when Flows given as parameters are ready to be displayed.
41+
/// Once it is the case, the block is executed
42+
///
43+
/// - Parameters:
44+
/// - flow1: first Flow to be observed
45+
/// - flow2: second Flow to be observed
46+
/// - flow3: third Flow to be observed
47+
/// - flow4: fourth Flow to be observed
48+
/// - flow5: fifth Flow to be observed
49+
/// - block: block to execute whenever the Flows are ready to use
50+
@available(*, deprecated, message: "You should use Flows.use()")
51+
static func whenReady<RootType1, RootType2, RootType3, RootType4, RootType5>(flow1: Flow,
52+
flow2: Flow,
53+
flow3: Flow,
54+
flow4: Flow,
55+
flow5: Flow,
56+
block: @escaping (_ flow1Root: RootType1,
57+
_ flow2Root: RootType2,
58+
_ flow3Root: RootType3,
59+
_ flow4Root: RootType4,
60+
_ flow5Root: RootType5) -> Void)
61+
where
62+
RootType1: UIViewController,
63+
RootType2: UIViewController,
64+
RootType3: UIViewController,
65+
RootType4: UIViewController,
66+
RootType5: UIViewController {
67+
guard
68+
let root1 = flow1.root as? RootType1,
69+
let root2 = flow2.root as? RootType2,
70+
let root3 = flow3.root as? RootType3,
71+
let root4 = flow4.root as? RootType4,
72+
let root5 = flow5.root as? RootType5 else {
73+
fatalError("Type mismatch, Flows roots types do not match the types awaited in the block")
74+
}
75+
76+
_ = Single.zip(flow1.rxFlowReady,
77+
flow2.rxFlowReady,
78+
flow3.rxFlowReady,
79+
flow4.rxFlowReady,
80+
flow5.rxFlowReady) { _, _, _, _, _ in Void() }
81+
.asDriver(onErrorJustReturn: Void())
82+
.drive(onNext: { _ in
83+
block(root1, root2, root3, root4, root5)
84+
})
85+
}
86+
87+
// swiftlint:enable function_parameter_count
88+
/// Allow to be triggered only when Flows given as parameters are ready to be displayed.
89+
/// Once it is the case, the block is executed
90+
///
91+
/// - Parameters:
92+
/// - flow1: first Flow to be observed
93+
/// - flow2: second Flow to be observed
94+
/// - flow3: third Flow to be observed
95+
/// - flow4: fourth Flow to be observed
96+
/// - block: block to execute whenever the Flows are ready to use
97+
@available(*, deprecated, message: "You should use Flows.use()")
98+
static func whenReady<RootType1, RootType2, RootType3, RootType4>(flow1: Flow,
99+
flow2: Flow,
100+
flow3: Flow,
101+
flow4: Flow,
102+
block: @escaping (_ flow1Root: RootType1,
103+
_ flow2Root: RootType2,
104+
_ flow3Root: RootType3,
105+
_ flow4Root: RootType4) -> Void)
106+
where
107+
RootType1: UIViewController,
108+
RootType2: UIViewController,
109+
RootType3: UIViewController,
110+
RootType4: UIViewController {
111+
guard
112+
let root1 = flow1.root as? RootType1,
113+
let root2 = flow2.root as? RootType2,
114+
let root3 = flow3.root as? RootType3,
115+
let root4 = flow4.root as? RootType4 else {
116+
fatalError("Type mismatch, Flows roots types do not match the types awaited in the block")
117+
}
118+
119+
_ = Single.zip(flow1.rxFlowReady,
120+
flow2.rxFlowReady,
121+
flow3.rxFlowReady,
122+
flow4.rxFlowReady) { _, _, _, _ in Void()
123+
}
124+
.asDriver(onErrorJustReturn: Void())
125+
.drive(onNext: { _ in
126+
block(root1, root2, root3, root4)
127+
})
128+
}
129+
130+
/// Allow to be triggered only when Flows given as parameters are ready to be displayed.
131+
/// Once it is the case, the block is executed
132+
///
133+
/// - Parameters:
134+
/// - flow1: first Flow to be observed
135+
/// - flow2: second Flow to be observed
136+
/// - flow3: third Flow to be observed
137+
/// - block: block to execute whenever the Flows are ready to use
138+
@available(*, deprecated, message: "You should use Flows.use()")
139+
static func whenReady<RootType1, RootType2, RootType3>(flow1: Flow,
140+
flow2: Flow,
141+
flow3: Flow,
142+
block: @escaping (_ flow1Root: RootType1,
143+
_ flow2Root: RootType2,
144+
_ flow3Root: RootType3) -> Void)
145+
where
146+
RootType1: UIViewController,
147+
RootType2: UIViewController,
148+
RootType3: UIViewController {
149+
guard
150+
let root1 = flow1.root as? RootType1,
151+
let root2 = flow2.root as? RootType2,
152+
let root3 = flow3.root as? RootType3 else {
153+
fatalError("Type mismatch, Flows roots types do not match the types awaited in the block")
154+
}
155+
156+
_ = Single.zip(flow1.rxFlowReady,
157+
flow2.rxFlowReady,
158+
flow3.rxFlowReady) { _, _, _ in Void() }
159+
.asDriver(onErrorJustReturn: Void())
160+
.drive(onNext: { _ in
161+
block(root1, root2, root3)
162+
})
163+
}
164+
165+
/// Allow to be triggered only when Flows given as parameters are ready to be displayed.
166+
/// Once it is the case, the block is executed
167+
///
168+
/// - Parameters:
169+
/// - flow1: first Flow to be observed
170+
/// - flow2: second Flow to be observed
171+
/// - block: block to execute whenever the Flows are ready to use
172+
@available(*, deprecated, message: "You should use Flows.use()")
173+
static func whenReady<RootType1: UIViewController, RootType2: UIViewController>(flow1: Flow,
174+
flow2: Flow,
175+
block: @escaping (_ flow1Root: RootType1,
176+
_ flow2Root: RootType2) -> Void) {
177+
guard let root1 = flow1.root as? RootType1,
178+
let root2 = flow2.root as? RootType2 else {
179+
fatalError("Type mismatch, Flows root types do not match the types awaited in the block")
180+
}
181+
182+
_ = Single.zip(flow1.rxFlowReady,
183+
flow2.rxFlowReady) { _, _ in Void() }
184+
.asDriver(onErrorJustReturn: Void())
185+
.drive(onNext: { _ in
186+
block(root1, root2)
187+
})
188+
}
189+
190+
/// Allow to be triggered only when Flow given as parameters is ready to be displayed.
191+
/// Once it is the case, the block is executed
192+
///
193+
/// - Parameters:
194+
/// - flow1: Flow to be observed
195+
/// - block: block to execute whenever the Flow is ready to use
196+
@available(*, deprecated, message: "You should use Flows.use()")
197+
static func whenReady<RootType: UIViewController>(flow1: Flow,
198+
block: @escaping (_ flowRoot1: RootType) -> Void) {
199+
guard let root = flow1.root as? RootType else {
200+
fatalError("Type mismatch, Flow root type does not match the type awaited in the block")
201+
}
202+
203+
_ = flow1
204+
.rxFlowReady
205+
.asDriver(onErrorJustReturn: true)
206+
.drive(onNext: { _ in
207+
block(root)
208+
})
209+
}
210+
}
211+
212+
#endif

0 commit comments

Comments
 (0)