Skip to content

Commit

Permalink
Add ViewEnvironment (#739)
Browse files Browse the repository at this point in the history
ViewEnvironment is a container of strongly-typed key/value pairs that can be
passed down the view-side of a workflow tree. This allows containers to provide
view-oriented hinting to their children as well as allows for view-side changes
to view-only environment without re-rendering the workflow tree (for example,
runtime theme changes).
  • Loading branch information
bencochran committed Mar 4, 2020
1 parent 485af5a commit 436627d
Show file tree
Hide file tree
Showing 28 changed files with 390 additions and 152 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import WorkflowUI
public final class BackStackContainer: ScreenViewController<BackStackScreen>, UINavigationControllerDelegate {
private let navController: UINavigationController

public required init(screen: BackStackScreen) {
public required init(screen: BackStackScreen, environment: ViewEnvironment) {
self.navController = UINavigationController()

super.init(screen: screen)
super.init(screen: screen, environment: environment)

update(with: screen)
}
Expand All @@ -42,7 +42,7 @@ public final class BackStackContainer: ScreenViewController<BackStackScreen>, UI
navController.view.frame = view.bounds
}

public override func screenDidChange(from previousScreen: BackStackScreen) {
public override func screenDidChange(from previousScreen: BackStackScreen, previousEnvironment: ViewEnvironment) {
update(with: screen)
}

Expand All @@ -63,10 +63,10 @@ public final class BackStackContainer: ScreenViewController<BackStackScreen>, UI
viewController.matches(item: item)
}) {
let existingViewController = existingViewControllers.remove(at: idx)
existingViewController.update(item: item)
existingViewController.update(item: item, environment: environment)
updatedViewControllers.append(existingViewController)
} else {
updatedViewControllers.append(ScreenWrapperViewController(item: item))
updatedViewControllers.append(ScreenWrapperViewController(item: item, environment: environment))
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ public struct BackStackScreen: Screen {
self.items = items
}

public var viewControllerDescription: ViewControllerDescription {
return BackStackContainer.description(for: self)
public func viewControllerDescription(environment: ViewEnvironment) -> ViewControllerDescription {
return BackStackContainer.description(for: self, environment: environment)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ import WorkflowUI
final class ScreenWrapperViewController: UIViewController {
let key: AnyHashable
let screenType: Any.Type
let environment: ViewEnvironment

let contentViewController: DescribedViewController

init(item: BackStackScreen.Item) {
init(item: BackStackScreen.Item, environment: ViewEnvironment) {
self.key = item.key
self.screenType = item.screenType
self.contentViewController = DescribedViewController(screen: item.screen)
self.environment = environment
self.contentViewController = DescribedViewController(screen: item.screen, environment: environment)

super.init(nibName: nil, bundle: nil)

Expand All @@ -51,8 +53,8 @@ final class ScreenWrapperViewController: UIViewController {
contentViewController.view.frame = view.bounds
}

func update(item: BackStackScreen.Item) {
contentViewController.update(screen: item.screen)
func update(item: BackStackScreen.Item, environment: ViewEnvironment) {
contentViewController.update(screen: item.screen, environment: environment)
update(barVisibility: item.barVisibility)
}

Expand Down
16 changes: 8 additions & 8 deletions swift/Samples/SampleApp/Sources/CrossFadeContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,18 @@ struct CrossFadeScreen: Screen {
return self.key == otherScreen.key
}

var viewControllerDescription: ViewControllerDescription {
return CrossFadeContainerViewController.description(for: self)
func viewControllerDescription(environment: ViewEnvironment) -> ViewControllerDescription {
return CrossFadeContainerViewController.description(for: self, environment: environment)
}
}


fileprivate final class CrossFadeContainerViewController: ScreenViewController<CrossFadeScreen> {
var childViewController: DescribedViewController

required init(screen: CrossFadeScreen) {
childViewController = DescribedViewController(screen: screen.baseScreen)
super.init(screen: screen)
required init(screen: CrossFadeScreen, environment: ViewEnvironment) {
childViewController = DescribedViewController(screen: screen.baseScreen, environment: environment)
super.init(screen: screen, environment: environment)
}

override func viewDidLoad() {
Expand All @@ -67,13 +67,13 @@ fileprivate final class CrossFadeContainerViewController: ScreenViewController<C
childViewController.view.frame = view.bounds
}

override func screenDidChange(from previousScreen: CrossFadeScreen) {
override func screenDidChange(from previousScreen: CrossFadeScreen, previousEnvironment: ViewEnvironment) {
if screen.isEquivalent(to: previousScreen) {
childViewController.update(screen: screen.baseScreen)
childViewController.update(screen: screen.baseScreen, environment: environment)
} else {
// The new screen is different than the previous. Animate the transition.
let oldChild = childViewController
childViewController = DescribedViewController(screen: screen.baseScreen)
childViewController = DescribedViewController(screen: screen.baseScreen, environment: environment)
addChild(childViewController)
view.addSubview(childViewController.view)
UIView.transition(
Expand Down
10 changes: 5 additions & 5 deletions swift/Samples/SampleApp/Sources/DemoScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ struct DemoScreen: Screen {
let isRefreshEnabled: Bool
let onRefreshTap: () -> Void

var viewControllerDescription: ViewControllerDescription {
return DemoViewController.description(for: self)
func viewControllerDescription(environment: ViewEnvironment) -> ViewControllerDescription {
return DemoViewController.description(for: self, environment: environment)
}
}

Expand All @@ -42,12 +42,12 @@ fileprivate final class DemoViewController: ScreenViewController<DemoScreen> {
private let statusLabel: UILabel
private let refreshButton: UIButton

required init(screen: DemoScreen) {
required init(screen: DemoScreen, environment: ViewEnvironment) {
titleButton = UIButton(frame: .zero)
subscribeButton = UIButton(frame: .zero)
statusLabel = UILabel(frame: .zero)
refreshButton = UIButton(frame: .zero)
super.init(screen: screen)
super.init(screen: screen, environment: environment)

update(with: screen)
}
Expand Down Expand Up @@ -106,7 +106,7 @@ fileprivate final class DemoViewController: ScreenViewController<DemoScreen> {
height: height)
}

override func screenDidChange(from previousScreen: DemoScreen) {
override func screenDidChange(from previousScreen: DemoScreen, previousEnvironment: ViewEnvironment) {
update(with: screen)
}

Expand Down
10 changes: 5 additions & 5 deletions swift/Samples/SampleApp/Sources/WelcomeScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ struct WelcomeScreen: Screen {
var onNameChanged: (String) -> Void
var onLoginTapped: () -> Void

var viewControllerDescription: ViewControllerDescription {
return WelcomeViewController.description(for: self)
func viewControllerDescription(environment: ViewEnvironment) -> ViewControllerDescription {
return WelcomeViewController.description(for: self, environment: environment)
}
}

Expand All @@ -33,11 +33,11 @@ fileprivate final class WelcomeViewController: ScreenViewController<WelcomeScree
let nameField: UITextField
let button: UIButton

required init(screen: WelcomeScreen) {
required init(screen: WelcomeScreen, environment: ViewEnvironment) {
welcomeLabel = UILabel(frame: .zero)
nameField = UITextField(frame: .zero)
button = UIButton(frame: .zero)
super.init(screen: screen)
super.init(screen: screen, environment: environment)

update(with: screen)
}
Expand Down Expand Up @@ -89,7 +89,7 @@ fileprivate final class WelcomeViewController: ScreenViewController<WelcomeScree
.insetBy(dx: inset, dy: 0.0)
}

override func screenDidChange(from previousScreen: WelcomeScreen) {
override func screenDidChange(from previousScreen: WelcomeScreen, previousEnvironment: ViewEnvironment) {
update(with: screen)
}

Expand Down
12 changes: 6 additions & 6 deletions swift/Samples/SplitScreenContainer/DemoApp/BarScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ struct BarScreen: Screen {
let backgroundColors: [UIColor]
let viewTapped: () -> Void

var viewControllerDescription: ViewControllerDescription {
return BarScreenViewController.description(for: self)
func viewControllerDescription(environment: ViewEnvironment) -> ViewControllerDescription {
return BarScreenViewController.description(for: self, environment: environment)
}
}

Expand All @@ -34,8 +34,8 @@ fileprivate final class BarScreenViewController: ScreenViewController<BarScreen>
private lazy var tapGestureRecognizer: UITapGestureRecognizer = .init()
private var gradientLayer: CAGradientLayer?

required init(screen: BarScreen) {
super.init(screen: screen)
required init(screen: BarScreen, environment: ViewEnvironment) {
super.init(screen: screen, environment: environment)

update(with: screen)
}
Expand All @@ -58,8 +58,8 @@ fileprivate final class BarScreenViewController: ScreenViewController<BarScreen>

updateGradient(for: view, colors: screen.backgroundColors)
}
override func screenDidChange(from previousScreen: BarScreen) {

override func screenDidChange(from previousScreen: BarScreen, previousEnvironment: ViewEnvironment) {
update(with: screen)
}

Expand Down
12 changes: 6 additions & 6 deletions swift/Samples/SplitScreenContainer/DemoApp/FooScreen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ struct FooScreen: Screen {
let backgroundColor: UIColor
let viewTapped: () -> Void

var viewControllerDescription: ViewControllerDescription {
return FooScreenViewController.description(for: self)
func viewControllerDescription(environment: ViewEnvironment) -> ViewControllerDescription {
return FooScreenViewController.description(for: self, environment: environment)
}
}

Expand All @@ -33,8 +33,8 @@ fileprivate final class FooScreenViewController: ScreenViewController<FooScreen>
private lazy var titleLabel: UILabel = .init()
private lazy var tapGestureRecognizer: UITapGestureRecognizer = .init()

required init(screen: FooScreen) {
super.init(screen: screen)
required init(screen: FooScreen, environment: ViewEnvironment) {
super.init(screen: screen, environment: environment)

update(with: screen)
}
Expand All @@ -54,8 +54,8 @@ fileprivate final class FooScreenViewController: ScreenViewController<FooScreen>
titleLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
override func screenDidChange(from previousScreen: FooScreen) {

override func screenDidChange(from previousScreen: FooScreen, previousEnvironment: ViewEnvironment) {
update(with: screen)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ class SplitScreenContainerScreenSnapshotTests: FBSnapshotTestCase {
)

let viewController = SplitScreenContainerViewController(
screen: splitScreenContainerScreen
screen: splitScreenContainerScreen,
environment: .empty
)
viewController.view.layoutIfNeeded()

Expand All @@ -44,8 +45,8 @@ fileprivate struct FooScreen: Screen {
let backgroundColor: UIColor
let viewTapped: () -> Void

var viewControllerDescription: ViewControllerDescription {
return FooScreenViewController.description(for: self)
func viewControllerDescription(environment: ViewEnvironment) -> ViewControllerDescription {
return FooScreenViewController.description(for: self, environment: environment)
}
}

Expand All @@ -55,8 +56,8 @@ fileprivate final class FooScreenViewController: ScreenViewController<FooScreen>
private lazy var titleLabel: UILabel = .init()
private lazy var tapGestureRecognizer: UITapGestureRecognizer = .init()

required init(screen: FooScreen) {
super.init(screen: screen)
required init(screen: FooScreen, environment: ViewEnvironment) {
super.init(screen: screen, environment: environment)

update(with: screen)
}
Expand All @@ -76,8 +77,8 @@ fileprivate final class FooScreenViewController: ScreenViewController<FooScreen>
titleLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
override func screenDidChange(from previousScreen: FooScreen) {

override func screenDidChange(from previousScreen: FooScreen, previousEnvironment: ViewEnvironment) {
update(with: screen)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2020 Square Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import WorkflowUI

public enum SplitScreenPosition {
/// Not appearing in a split screen context
case none

/// Appearing in the leading position in a split screen
case leading

/// Appearing in the trailing position in a split screen
case trailing
}

extension ViewEnvironment {

internal(set) public var splitScreenPosition: SplitScreenPosition {
get { return self[SplitScreenPositionKey.self] }
set { self[SplitScreenPositionKey.self] = newValue }
}

}

private enum SplitScreenPositionKey: ViewEnvironmentKey {
static var defaultValue: SplitScreenPosition = .none
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ public struct SplitScreenContainerScreen<LeadingScreenType: Screen, TrailingScre
self.separatorWidth = separatorWidth
}

public var viewControllerDescription: ViewControllerDescription {
return SplitScreenContainerViewController.description(for: self)
public func viewControllerDescription(environment: ViewEnvironment) -> ViewControllerDescription {
return SplitScreenContainerViewController.description(for: self, environment: environment)
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,21 @@ internal final class SplitScreenContainerViewController<LeadingScreenType: Scree

private var needsAnimatedLayout = false

required init(screen: ContainerScreen) {
leadingContentViewController = DescribedViewController(screen: screen.leadingScreen)
trailingContentViewController = DescribedViewController(screen: screen.trailingScreen)
super.init(screen: screen)
required init(screen: ContainerScreen, environment: ViewEnvironment) {
leadingContentViewController = DescribedViewController(
screen: screen.leadingScreen,
environment: environment
.setting(keyPath: \.splitScreenPosition, to: .leading)
)
trailingContentViewController = DescribedViewController(
screen: screen.trailingScreen,
environment: environment
.setting(keyPath: \.splitScreenPosition, to: .trailing)
)
super.init(screen: screen, environment: environment)
}

override internal func screenDidChange(from previousScreen: ContainerScreen) {
override internal func screenDidChange(from previousScreen: ContainerScreen, previousEnvironment: ViewEnvironment) {
if screen.ratio != previousScreen.ratio {
needsAnimatedLayout = true
}
Expand All @@ -48,8 +56,16 @@ internal final class SplitScreenContainerViewController<LeadingScreenType: Scree
private func update(with screen: ContainerScreen) {
separatorView.backgroundColor = screen.separatorColor

leadingContentViewController.update(screen: screen.leadingScreen)
trailingContentViewController.update(screen: screen.trailingScreen)
leadingContentViewController.update(
screen: screen.leadingScreen,
environment: environment
.setting(keyPath: \.splitScreenPosition, to: .leading)
)
trailingContentViewController.update(
screen: screen.trailingScreen,
environment: environment
.setting(keyPath: \.splitScreenPosition, to: .trailing)
)

//Intentional force of layout pass after updating the child view controllers
view.layoutIfNeeded()
Expand Down
Loading

0 comments on commit 436627d

Please sign in to comment.