Add SplitScreenContainer
ADD
WorkflowSplitScreenContainer.podspec
+Pod::Spec.new do |s|
+ s.name = 'WorkflowSplitScreenContainer'
+ s.version = '0.22.4' #TODO: Change to latest version number.
+ s.summary = 'See the README.'
+ s.homepage = 'https://www.github.com/square/workflow'
+ s.license = 'Apache License, Version 2.0'
+ s.author = 'Square'
+ s.source = { :git => 'https://github.com/square/workflow.git', :tag => "v#{s.version}" }
+
+ # 1.7 is needed for `swift_versions` support
+ s.cocoapods_version = '>= 1.7.0.beta.1'
+
+ s.swift_versions = ['5.0']
+ s.ios.deployment_target = '9.3'
+ s.osx.deployment_target = '10.12'
+
+ s.source_files = 'swift/WorkflowSplitScreenContainer/Sources/**/*.swift'
+
+ s.dependency 'Workflow', "#{s.version}"
+ s.dependency 'WorkflowUI', "#{s.version}"
+
+ s.app_spec 'DemoApp' do |app_spec|
+ app_spec.source_files = 'swift/WorkflowSplitScreenContainer/DemoApp/**/*.swift'
+ end
+
+end

ADD
swift/WorkflowSplitScreenContainer/DemoApp/AppDelegate.swift
+/*
+ * Copyright 2019 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 UIKit
+import Workflow
+import WorkflowUI
+
+
+@UIApplicationMain
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ var window: UIWindow?
+
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+ let window = UIWindow(frame: UIScreen.main.bounds)
+
+ var viewRegistry = ViewRegistry()
+ viewRegistry.registerSplitScreenContainer()
+ viewRegistry.registerBaseScreen()
+
+ let container = ContainerViewController(
+ workflow: DemoWorkflow(),
+ viewRegistry: viewRegistry
+ )
+
+ window.rootViewController = container
+ self.window = window
+ window.makeKeyAndVisible()
+ return true
+ }
+
+}

ADD
swift/WorkflowSplitScreenContainer/DemoApp/BaseScreen.swift
+/*
+ * Copyright 2019 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 Workflow
+import WorkflowUI
+
+
+struct BaseScreen: Screen {
+ var title: String
+ var backgroundColor: UIColor
+}
+
+
+extension ViewRegistry {
+
+ public mutating func registerBaseScreen() {
+ self.register(screenViewControllerType: BaseScreenViewController.self)
+ }
+
+}
+
+
+fileprivate final class BaseScreenViewController: ScreenViewController<BaseScreen> {
+
+ private let titleLabel: UILabel
+
+ required init(screen: BaseScreen, viewRegistry: ViewRegistry) {
+ titleLabel = UILabel(frame: .zero)
+ super.init(screen: screen, viewRegistry: viewRegistry)
+
+ update(with: screen)
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ view.addSubview(titleLabel)
+ }
+
+ override func viewDidLayoutSubviews() {
+ super.viewDidLayoutSubviews()
+
+ let height: CGFloat = 44.0
+
+ var (top, bottom) = view.bounds.divided(atDistance: view.bounds.height / 2, from: CGRectEdge.minYEdge)
+
+ top.size.height -= (height / 2.0)
+ bottom.origin.y += (height)
+ bottom.size.height -= (height / 2.0)
+
+ titleLabel.frame = top
+ }
+
+ override func screenDidChange(from previousScreen: BaseScreen) {
+ update(with: screen)
+ }
+
+ private func update(with screen: BaseScreen) {
+ view.backgroundColor = screen.backgroundColor
+ titleLabel.text = screen.title
+ }
+}
+

ADD
swift/WorkflowSplitScreenContainer/DemoApp/DemoWorkflow.swift
+/*
+ * Copyright 2019 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 Workflow
+import WorkflowUI
+import WorkflowSplitScreenContainer
+
+
+// MARK: Input and Output
+
+struct DemoWorkflow: Workflow {
+
+ typealias Output = Never
+
+}
+
+
+// MARK: State and Initialization
+
+extension DemoWorkflow {
+
+ typealias State = Void
+
+ func makeInitialState() -> State {
+ return ()
+ }
+
+ func workflowDidChange(from previousWorkflow: DemoWorkflow, state: inout State) {
+ }
+
+}
+
+
+// MARK: Rendering
+
+extension DemoWorkflow {
+
+ typealias Rendering = SplitScreenContainerScreen
+
+ func render(state: State, context: RenderContext<DemoWorkflow>) -> Rendering {
+
+ return SplitScreenContainerScreen(
+ leftScreen: BaseScreen(title: "Left screen", backgroundColor: .red),
+ rightScreen: BaseScreen(title: "Right screen", backgroundColor: .green),
+ ratio: .third
+ )
+
+ }
+
+}

ADD
swift/WorkflowSplitScreenContainer/README.md
+# WorkflowSplitScreenContainer
+
+Container to display two screens side by side.

ADD
swift/WorkflowSplitScreenContainer/Sources/SplitScreenContainerScreen.swift
+/*
+ * Copyright 2019 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
+
+
+/// A `SplitScreenContainerScreen` displays two screens side by side with a separator in between.
+public struct SplitScreenContainerScreen: Screen {
+
+ /// The screen displayed to the left of the separator.
+ public var leftScreen: AnyScreen
+
+ /// The screen displayed to the right of the separator.
+ public var rightScreen: AnyScreen
+
+ /// The ratio of `leftScreen`'s width relative to that of `rightScreen`. Defaults to `.third`.
+ public var ratio: Ratio
+
+ public init<LeftScreenType: Screen, RightScreenType: Screen>(
+ leftScreen: LeftScreenType,
+ rightScreen: RightScreenType,
+ ratio: Ratio = .third
+ ) {
+ self.leftScreen = AnyScreen(leftScreen)
+ self.rightScreen = AnyScreen(rightScreen)
+ self.ratio = ratio
+ }
+
+}
+
+
+extension SplitScreenContainerScreen {
+
+ public struct Ratio {
+
+ public var value: CGFloat
+
+ public init(_ value: CGFloat) {
+ self.value = value
+ }
+
+ public static let quarter = Ratio(1.0 / 4.0)
+ public static let third = Ratio(1.0 / 3.0)
+ public static let half = Ratio(1.0 / 2.0)
+
+ }
+
+}

ADD
swift/WorkflowSplitScreenContainer/Sources/SplitScreenContainerViewController.swift
+/*
+ * Copyright 2019 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 Workflow
+import WorkflowUI
+
+extension ViewRegistry {
+
+ public mutating func registerSplitScreenContainer() {
+ self.register(screenViewControllerType: SplitScreenContainerViewController.self)
+ }
+
+}
+
+
+final class SplitScreenContainerViewController: ScreenViewController<SplitScreenContainerScreen> {
+
+ var leftContentViewController: ScreenViewController<AnyScreen>? = nil
+ var leftContainerView: UIView
+
+ var separatorView: UIView
+
+ var rightContentViewController: ScreenViewController<AnyScreen>? = nil
+ var rightContainerView: UIView
+
+ required init(screen: SplitScreenContainerScreen, viewRegistry: ViewRegistry) {
+ self.leftContainerView = UIView()
+ self.leftContainerView.translatesAutoresizingMaskIntoConstraints = false
+
+ self.separatorView = UIView()
+ self.separatorView.translatesAutoresizingMaskIntoConstraints = false
+ self.separatorView.backgroundColor = .black
+
+ self.rightContainerView = UIView()
+ self.rightContainerView.translatesAutoresizingMaskIntoConstraints = false
+
+ super.init(screen: screen, viewRegistry: viewRegistry)
+
+ update(with: screen)
+ }
+
+ override func screenDidChange(from previousScreen: SplitScreenContainerScreen) {
+ update(with: screen)
+ }
+
+ private func update(with screen: SplitScreenContainerScreen) {
+
+ func embed(_ screen: AnyScreen, in containerView: UIView) -> ScreenViewController<AnyScreen> {
+ let viewController = viewRegistry.provideView(for: screen)
+ addChild(viewController)
+ containerView.addSubview(viewController.view)
+ viewController.didMove(toParent: self)
+
+ return viewController
+ }
+
+ if let leftContentViewController = leftContentViewController {
+ if leftContentViewController.screenType == type(of: screen.leftScreen) {
+ leftContentViewController.update(screen: screen.leftScreen)
+ } else {
+ leftContentViewController.willMove(toParent: nil)
+ leftContentViewController.view.removeFromSuperview()
+ leftContentViewController.removeFromParent()
+
+ self.leftContentViewController = embed(screen.leftScreen, in: leftContainerView)
+ }
+ } else {
+ self.leftContentViewController = embed(screen.leftScreen, in: leftContainerView)
+ }
+
+ if let rightContentViewController = rightContentViewController {
+ if rightContentViewController.screenType == type(of: screen.rightScreen) {
+ rightContentViewController.update(screen: screen.rightScreen)
+ } else {
+ rightContentViewController.willMove(toParent: nil)
+ rightContentViewController.view.removeFromSuperview()
+ rightContentViewController.removeFromParent()
+
+ self.rightContentViewController = embed(screen.rightScreen, in: rightContainerView)
+ }
+ } else {
+ self.rightContentViewController = embed(screen.rightScreen, in: rightContainerView)
+ }
+
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ view.addSubview(leftContainerView)
+ view.addSubview(separatorView)
+ view.addSubview(rightContainerView)
+
+ if let leftContentViewController = leftContentViewController {
+ leftContainerView.addSubview(leftContentViewController.view)
+ }
+
+ if let rightContentViewController = rightContentViewController {
+ rightContainerView.addSubview(rightContentViewController.view)
+ }
+
+ NSLayoutConstraint.activate([
+ leftContainerView.topAnchor.constraint(equalTo: view.topAnchor),
+ leftContainerView.leftAnchor.constraint(equalTo: view.leftAnchor),
+ leftContainerView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+ leftContainerView.rightAnchor.constraint(equalTo: separatorView.leftAnchor),
+ leftContainerView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: screen.ratio.value),
+
+ separatorView.topAnchor.constraint(equalTo: view.topAnchor),
+ separatorView.widthAnchor.constraint(equalToConstant: 1.0),
+ separatorView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+
+ rightContainerView.topAnchor.constraint(equalTo: view.topAnchor),
+ rightContainerView.leftAnchor.constraint(equalTo: separatorView.rightAnchor),
+ rightContainerView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+ rightContainerView.rightAnchor.constraint(equalTo: view.rightAnchor),
+ ])
+ }
+
+ override func viewDidLayoutSubviews() {
+ super.viewDidLayoutSubviews()
+
+ leftContentViewController?.view.frame = leftContainerView.bounds
+ rightContentViewController?.view.frame = rightContainerView.bounds
+ }
+}

Add SplitScreenContainer