Skip to content

Commit ccece24

Browse files
authored
[feat]: add ViewEnvironmentUI and support automatic bridging in WorkflowUI (#215)
1 parent fa31cbe commit ccece24

24 files changed

+1446
-23
lines changed

Diff for: Development.podspec

+7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Pod::Spec.new do |s|
1818
s.dependency 'WorkflowCombine'
1919
s.dependency 'WorkflowConcurrency'
2020
s.dependency 'ViewEnvironment'
21+
s.dependency 'ViewEnvironmentUI'
2122

2223
s.source_files = 'Samples/Dummy.swift'
2324

@@ -101,6 +102,12 @@ Pod::Spec.new do |s|
101102
}
102103
end
103104

105+
s.test_spec 'ViewEnvironmentUITests' do |test_spec|
106+
test_spec.requires_app_host = true
107+
test_spec.source_files = 'ViewEnvironmentUI/Tests/**/*.swift'
108+
test_spec.framework = 'XCTest'
109+
end
110+
104111
s.test_spec 'WorkflowTests' do |test_spec|
105112
test_spec.requires_app_host = true
106113
test_spec.source_files = 'Workflow/Tests/**/*.swift'

Diff for: Package.swift

+16-1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,13 @@ let package = Package(
8282
name: "ViewEnvironment",
8383
targets: ["ViewEnvironment"]
8484
),
85+
86+
// MARK: ViewEnvironmentUI
87+
88+
.library(
89+
name: "ViewEnvironmentUI",
90+
targets: ["ViewEnvironmentUI"]
91+
),
8592
],
8693
dependencies: [
8794
.package(url: "https://github.com/ReactiveCocoa/ReactiveSwift.git", from: "7.1.1"),
@@ -116,7 +123,7 @@ let package = Package(
116123

117124
.target(
118125
name: "WorkflowUI",
119-
dependencies: ["Workflow", "ViewEnvironment"],
126+
dependencies: ["Workflow", "ViewEnvironment", "ViewEnvironmentUI"],
120127
path: "WorkflowUI/Sources"
121128
),
122129
.testTarget(
@@ -228,6 +235,14 @@ let package = Package(
228235
name: "ViewEnvironment",
229236
path: "ViewEnvironment/Sources"
230237
),
238+
239+
// MARK: ViewEnvironmentUI
240+
241+
.target(
242+
name: "ViewEnvironmentUI",
243+
dependencies: ["ViewEnvironment"],
244+
path: "ViewEnvironmentUI/Sources"
245+
),
231246
],
232247
swiftLanguageVersions: [.v5]
233248
)

Diff for: RELEASING.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ For Squares, membership is managed through the `Workflow Swift Owners` registry
1717

1818
> ⚠️ [Optional] To avoid possible headaches when publishing podspecs, validation can be performed before updating the Workflow version number(s). To do this, run the following in the root directory of this repo:
1919
> ```bash
20-
> bundle exec pod lib lint Workflow.podspec ViewEnvironment.podspec WorkflowTesting.podspec WorkflowReactiveSwift.podspec WorkflowUI.podspec WorkflowRxSwift.podspec WorkflowReactiveSwiftTesting.podspec WorkflowRxSwiftTesting.podspec WorkflowSwiftUI.podspec WorkflowCombine.podspec WorkflowCombineTesting.podspec WorkflowConcurrency.podspec WorkflowConcurrencyTesting.podspec
20+
> bundle exec pod lib lint Workflow.podspec ViewEnvironment.podspec ViewEnvironmentUI.podspec WorkflowTesting.podspec WorkflowReactiveSwift.podspec WorkflowUI.podspec WorkflowRxSwift.podspec WorkflowReactiveSwiftTesting.podspec WorkflowRxSwiftTesting.podspec WorkflowSwiftUI.podspec WorkflowCombine.podspec WorkflowCombineTesting.podspec WorkflowConcurrency.podspec WorkflowConcurrencyTesting.podspec
2121
> ```
2222
2323
1. Update `VERSION` file based on [`semver`](https://semver.org/).
@@ -37,6 +37,7 @@ For Squares, membership is managed through the `Workflow Swift Owners` registry
3737
bundle exec pod trunk push WorkflowTesting.podspec --synchronous
3838
bundle exec pod trunk push WorkflowReactiveSwift.podspec --synchronous
3939
bundle exec pod trunk push ViewEnvironment.podspec --synchronous
40+
bundle exec pod trunk push ViewEnvironmentUI.podspec --synchronous
4041
bundle exec pod trunk push WorkflowUI.podspec --synchronous
4142
bundle exec pod trunk push WorkflowRxSwift.podspec --synchronous
4243
bundle exec pod trunk push WorkflowReactiveSwiftTesting.podspec --synchronous

Diff for: Samples/TicTacToe/Sources/Authentication/LoginScreen.swift

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ struct LoginScreen: Screen {
2727

2828
func viewControllerDescription(environment: ViewEnvironment) -> ViewControllerDescription {
2929
return ViewControllerDescription(
30+
environment: environment,
3031
build: { LoginViewController() },
3132
update: { $0.update(with: self) }
3233
)

Diff for: Samples/Tutorial/Podfile

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ target 'Tutorial' do
77
pod 'Workflow', path: '../../Workflow.podspec', :testspecs => ['Tests']
88
pod 'WorkflowUI', path: '../../WorkflowUI.podspec', :testspecs => ['Tests']
99
pod 'ViewEnvironment', path: '../../ViewEnvironment.podspec'
10+
pod 'ViewEnvironmentUI', path: '../../ViewEnvironmentUI.podspec', :testspecs => ['Tests']
1011
pod 'WorkflowReactiveSwift', path: '../../WorkflowReactiveSwift.podspec', :testspecs => ['Tests']
1112
pod 'BackStackContainer', path: '../BackStackContainer/BackStackContainer.podspec'
1213

Diff for: ViewEnvironmentUI.podspec

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
require_relative('version')
2+
3+
Pod::Spec.new do |s|
4+
s.name = 'ViewEnvironmentUI'
5+
s.version = WORKFLOW_VERSION
6+
s.summary = 'Provides a way to propagate a ViewEnvironment through an imperative hierarchy'
7+
s.homepage = 'https://www.github.com/square/workflow-swift'
8+
s.license = 'Apache License, Version 2.0'
9+
s.author = 'Square'
10+
s.source = { :git => 'https://github.com/square/workflow-swift.git', :tag => "v#{s.version}" }
11+
12+
# 1.7 is needed for `swift_versions` support
13+
s.cocoapods_version = '>= 1.7.0'
14+
15+
s.swift_versions = [WORKFLOW_SWIFT_VERSION]
16+
s.ios.deployment_target = WORKFLOW_IOS_DEPLOYMENT_TARGET
17+
s.osx.deployment_target = WORKFLOW_MACOS_DEPLOYMENT_TARGET
18+
19+
s.source_files = 'ViewEnvironmentUI/Sources/**/*.swift'
20+
21+
s.dependency 'ViewEnvironment', "#{s.version}"
22+
23+
s.pod_target_xcconfig = { 'APPLICATION_EXTENSION_API_ONLY' => 'YES' }
24+
25+
s.test_spec 'Tests' do |test_spec|
26+
test_spec.source_files = 'ViewEnvironmentUI/Tests/**/*.swift'
27+
test_spec.framework = 'XCTest'
28+
test_spec.library = 'swiftos'
29+
30+
# Create an app host so that we can host
31+
# view or view controller based tests in a real environment.
32+
test_spec.requires_app_host = true
33+
34+
test_spec.pod_target_xcconfig = { 'APPLICATION_EXTENSION_API_ONLY' => 'NO' }
35+
end
36+
end

Diff for: ViewEnvironmentUI/README.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# ViewEnvironmentUI
2+
3+
`ViewEnvironmentUI` provides a means to propagate a `ViewEnvironment` through a hierarchy of object nodes.
4+
5+
Support for propagation of `ViewEnvironment` through `UIViewController`s and `UIView`s is provided by this framework.
6+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2023 Square Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#if canImport(UIKit)
18+
19+
import UIKit
20+
import ViewEnvironment
21+
22+
extension UIView: ViewEnvironmentPropagating {
23+
@_spi(ViewEnvironmentWiring)
24+
public var defaultEnvironmentAncestor: ViewEnvironmentPropagating? { superview }
25+
26+
@_spi(ViewEnvironmentWiring)
27+
public var defaultEnvironmentDescendants: [ViewEnvironmentPropagating] { subviews }
28+
29+
@_spi(ViewEnvironmentWiring)
30+
public func setNeedsApplyEnvironment() {
31+
setNeedsLayout()
32+
}
33+
}
34+
35+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2023 Square Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#if canImport(UIKit)
18+
19+
import UIKit
20+
import ViewEnvironment
21+
22+
extension UIViewController: ViewEnvironmentPropagating {
23+
@_spi(ViewEnvironmentWiring)
24+
public var defaultEnvironmentAncestor: ViewEnvironmentPropagating? { parent ?? presentingViewController }
25+
26+
@_spi(ViewEnvironmentWiring)
27+
public var defaultEnvironmentDescendants: [ViewEnvironmentPropagating] {
28+
var descendants = children
29+
30+
if let presentedViewController = presentedViewController {
31+
descendants.append(presentedViewController)
32+
}
33+
34+
return descendants
35+
}
36+
37+
@_spi(ViewEnvironmentWiring)
38+
public func setNeedsApplyEnvironment() {
39+
viewIfLoaded?.setNeedsLayout()
40+
}
41+
}
42+
43+
#endif
+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2023 Square Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import ViewEnvironment
18+
19+
/// `ViewEnvironmentObserving` allows an environment propagation node to observe updates to the
20+
/// `ViewEnvironment` as it flows through the node hierarchy and have
21+
/// the environment applied to the node.
22+
///
23+
/// For example, for a `UIViewController` hierarchy observing `ViewEnvironment`:
24+
/// ```swift
25+
/// final class MyViewController:
26+
/// UIViewController, ViewEnvironmentObserving
27+
/// {
28+
/// override func viewWillLayoutSubviews() {
29+
/// super.viewWillLayoutSubviews()
30+
///
31+
/// // You _must_ call this function in viewWillLayoutSubviews()
32+
/// applyEnvironmentIfNeeded()
33+
/// }
34+
///
35+
/// func apply(environment: ViewEnvironment) {
36+
/// // Apply values from the environment to your view controller (e.g. a theme)
37+
/// }
38+
///
39+
/// // If you'd like to override values in the environment you can provide them here. If you'd
40+
/// // like to just inherit the context from above there is no need to implement this function.
41+
/// func customize(environment: inout ViewEnvironment) {
42+
/// environment.traits.mode = .dark
43+
/// }
44+
/// }
45+
/// ```
46+
///
47+
/// - Important: `UIViewController` and `UIView` conformers _must_ call `applyEnvironmentIfNeeded()`
48+
/// in `viewWillLayoutSubviews()` and `layoutSubviews()` respectively.
49+
///
50+
public protocol ViewEnvironmentObserving: ViewEnvironmentPropagating {
51+
/// Customizes the `ViewEnvironment` as it flows through this propagation node to provide overrides to environment
52+
/// values. These customizations will apply to the environment on this node (e.g. `self.environment` and the
53+
/// parameter of `apply(environment:)`) and will be propagated to all descendant nodes.
54+
///
55+
/// If you'd like to just inherit the environment from above, leave this function body empty.
56+
///
57+
func customize(environment: inout ViewEnvironment)
58+
59+
/// Consumers should apply the `ViewEnvironment` to their node when this function is called.
60+
///
61+
func apply(environment: ViewEnvironment)
62+
63+
/// Consumers must call this function when environment updates should be applied.
64+
///
65+
/// This will call `apply(environment:)` on the receiver if the node has been flagged for needing update.
66+
///
67+
/// When working with `UIViewController` and `UIView` nodes, consumers _must_ call this function in
68+
/// `viewWillLayoutSubviews()` for `UIViewController`s and `layoutSubviews()` for `UIView`s.
69+
///
70+
func applyEnvironmentIfNeeded()
71+
72+
/// Called when the environment has been set for needing update, but before it has been applied.
73+
///
74+
/// This may be called frequently when compared to `apply(environment:)` which should only be called
75+
/// when it's appropriate to apply the environment to the backing object (e.g. `viewWillLayoutSubviews`).
76+
///
77+
func environmentDidChange()
78+
}
79+
80+
extension ViewEnvironmentObserving {
81+
public func customize(environment: inout ViewEnvironment) {}
82+
83+
public func apply(environment: ViewEnvironment) {}
84+
85+
public func environmentDidChange() {}
86+
}

0 commit comments

Comments
 (0)