Make BackStackScreen generic
Part of
MODIFY
swift/Samples/BackStackContainer/Sources/BackStackContainer.swift
swift/Samples/BackStackContainer/Sources/BackStackContainer.swift
import WorkflowUI
-public final class BackStackContainer: ScreenViewController<BackStackScreen>, UINavigationControllerDelegate {
+public final class BackStackContainer<Content: Screen>: ScreenViewController<BackStackScreen<Content>>, UINavigationControllerDelegate {
private let navController: UINavigationController
- public required init(screen: BackStackScreen, environment: ViewEnvironment) {
+ public required init(screen: BackStackScreen<Content>, environment: ViewEnvironment) {
self.navController = UINavigationController()
super.init(screen: screen, environment: environment)
@@ -42,7 +42,7 @@ public final class BackStackContainer: ScreenViewController<BackStackScreen>, UI
navController.view.frame = view.bounds
}
- override public func screenDidChange(from previousScreen: BackStackScreen, previousEnvironment: ViewEnvironment) {
+ override public func screenDidChange(from previousScreen: BackStackScreen<Content>, previousEnvironment: ViewEnvironment) {
update(with: screen)
}
@@ -54,9 +54,9 @@ public final class BackStackContainer: ScreenViewController<BackStackScreen>, UI
// MARK: - Private Methods
- private func update(with screen: BackStackScreen) {
- var existingViewControllers: [ScreenWrapperViewController] = navController.viewControllers as! [ScreenWrapperViewController]
- var updatedViewControllers: [ScreenWrapperViewController] = []
+ private func update(with screen: BackStackScreen<Content>) {
+ var existingViewControllers: [ScreenWrapperViewController<Content>] = navController.viewControllers as! [ScreenWrapperViewController<Content>]
+ var updatedViewControllers: [ScreenWrapperViewController<Content>] = []
for item in screen.items {
if let idx = existingViewControllers.firstIndex(where: { viewController -> Bool in
@@ -73,7 +73,7 @@ public final class BackStackContainer: ScreenViewController<BackStackScreen>, UI
navController.setViewControllers(updatedViewControllers, animated: true)
}
- private func setNavigationBarVisibility(with screen: BackStackScreen, animated: Bool) {
+ private func setNavigationBarVisibility(with screen: BackStackScreen<Content>, animated: Bool) {
guard let topScreen = screen.items.last else {
return
}

MODIFY
swift/Samples/BackStackContainer/Sources/BackStackScreen.swift
swift/Samples/BackStackContainer/Sources/BackStackScreen.swift
import WorkflowUI
-public struct BackStackScreen: Screen {
+public struct BackStackScreen<ScreenType: Screen>: Screen {
var items: [Item]
- public init(items: [BackStackScreen.Item]) {
+ public init(items: [Item]) {
self.items = items
}
@@ -32,13 +32,11 @@ extension BackStackScreen {
/// A specific item in the back stack. The key and screen type is used to differentiate reused vs replaced screens.
public struct Item {
public var key: AnyHashable
- public var screen: AnyScreen
- var screenType: Any.Type
+ public var screen: ScreenType
public var barVisibility: BarVisibility
- public init<ScreenType: Screen, Key: Hashable>(key: Key?, screen: ScreenType, barVisibility: BarVisibility) {
- self.screen = AnyScreen(screen)
- self.screenType = ScreenType.self
+ public init<Key: Hashable>(key: Key?, screen: ScreenType, barVisibility: BarVisibility) {
+ self.screen = screen
if let key = key {
self.key = AnyHashable(key)
@@ -48,26 +46,26 @@ extension BackStackScreen {
self.barVisibility = barVisibility
}
- public init<ScreenType: Screen>(screen: ScreenType, barVisibility: BarVisibility) {
+ public init(screen: ScreenType, barVisibility: BarVisibility) {
let key = Optional<AnyHashable>.none
self.init(key: key, screen: screen, barVisibility: barVisibility)
}
- public init<ScreenType: Screen, Key: Hashable>(key: Key?, screen: ScreenType, barContent: BackStackScreen.BarContent) {
+ public init<Key: Hashable>(key: Key?, screen: ScreenType, barContent: BackStackScreen.BarContent) {
self.init(key: key, screen: screen, barVisibility: .visible(barContent))
}
- public init<ScreenType: Screen>(screen: ScreenType, barContent: BackStackScreen.BarContent) {
+ public init(screen: ScreenType, barContent: BackStackScreen.BarContent) {
let key = Optional<AnyHashable>.none
self.init(key: key, screen: screen, barContent: barContent)
}
- public init<ScreenType: Screen, Key: Hashable>(key: Key?, screen: ScreenType) {
+ public init<Key: Hashable>(key: Key?, screen: ScreenType) {
let barVisibility: BarVisibility = .visible(BarContent())
self.init(key: key, screen: screen, barVisibility: barVisibility)
}
- public init<ScreenType: Screen>(screen: ScreenType) {
+ public init(screen: ScreenType) {
let key = Optional<AnyHashable>.none
self.init(key: key, screen: screen)
}

MODIFY
swift/Samples/BackStackContainer/Sources/ScreenWrapperViewController.swift
swift/Samples/BackStackContainer/Sources/ScreenWrapperViewController.swift
@@ -19,16 +19,14 @@ import WorkflowUI
/**
Wrapper view controller for being hosted in a backstack. Handles updating the bar button items.
*/
-final class ScreenWrapperViewController: UIViewController {
+final class ScreenWrapperViewController<ScreenType: Screen>: UIViewController {
let key: AnyHashable
- let screenType: Any.Type
let environment: ViewEnvironment
let contentViewController: DescribedViewController
- init(item: BackStackScreen.Item, environment: ViewEnvironment) {
+ init(item: BackStackScreen<ScreenType>.Item, environment: ViewEnvironment) {
self.key = item.key
- self.screenType = item.screenType
self.environment = environment
self.contentViewController = DescribedViewController(screen: item.screen, environment: environment)
@@ -53,17 +51,17 @@ final class ScreenWrapperViewController: UIViewController {
contentViewController.view.frame = view.bounds
}
- func update(item: BackStackScreen.Item, environment: ViewEnvironment) {
+ func update(item: BackStackScreen<ScreenType>.Item, environment: ViewEnvironment) {
contentViewController.update(screen: item.screen, environment: environment)
update(barVisibility: item.barVisibility)
}
- func matches(item: BackStackScreen.Item) -> Bool {
+ func matches(item: BackStackScreen<ScreenType>.Item) -> Bool {
return item.key == key
- && item.screenType == screenType
+ && type(of: item.screen) == ScreenType.self
}
- private func update(barVisibility: BackStackScreen.BarVisibility) {
+ private func update(barVisibility: BackStackScreen<ScreenType>.BarVisibility) {
navigationItem.setHidesBackButton(true, animated: false)
guard case let .visible(barContent) = barVisibility else {
@@ -113,10 +111,13 @@ final class ScreenWrapperViewController: UIViewController {
}
}
+// MARK: -
+
+extension ScreenWrapperViewController {
final class CallbackBarButtonItem: UIBarButtonItem {
var handler: () -> Void
- init(button: BackStackScreen.BarContent.Button) {
+ init(button: BackStackScreen<ScreenType>.BarContent.Button) {
self.handler = {}
super.init()
@@ -129,7 +130,7 @@ final class CallbackBarButtonItem: UIBarButtonItem {
fatalError("init(coder:) has not been implemented")
}
- func update(with button: BackStackScreen.BarContent.Button) {
+ func update(with button: BackStackScreen<ScreenType>.BarContent.Button) {
switch button.content {
case let .text(title):
self.title = title
@@ -145,3 +146,4 @@ final class CallbackBarButtonItem: UIBarButtonItem {
handler()
}
}
+}

MODIFY
swift/Samples/TicTacToe/Sources/Authentication/AuthenticationWorkflow.swift
swift/Samples/TicTacToe/Sources/Authentication/AuthenticationWorkflow.swift
@@ -161,12 +161,12 @@ extension AuthenticationWorkflow {
// MARK: Rendering
extension AuthenticationWorkflow {
- typealias Rendering = AlertContainerScreen<ModalContainerScreen<BackStackScreen>>
+ typealias Rendering = AlertContainerScreen<ModalContainerScreen<BackStackScreen<AnyScreen>>>
func render(state: AuthenticationWorkflow.State, context: RenderContext<AuthenticationWorkflow>) -> Rendering {
let sink = context.makeSink(of: Action.self)
- var backStackItems: [BackStackScreen.Item] = []
+ var backStackItems: [BackStackScreen<AnyScreen>.Item] = []
var modals: [ModalContainerScreenModal] = []
var alert: Alert?
@@ -176,7 +176,7 @@ extension AuthenticationWorkflow {
return .login(email: email, password: password)
}
}.rendered(with: context)
- backStackItems.append(BackStackScreen.Item(screen: loginScreen, barVisibility: .hidden))
+ backStackItems.append(BackStackScreen.Item(screen: AnyScreen(loginScreen), barVisibility: .hidden))
switch state {
case .emailPassword:
@@ -233,7 +233,7 @@ extension AuthenticationWorkflow {
)
}
- private func twoFactorScreen(error: AuthenticationService.AuthenticationError?, intermediateSession: String, sink: Sink<Action>) -> BackStackScreen.Item {
+ private func twoFactorScreen(error: AuthenticationService.AuthenticationError?, intermediateSession: String, sink: Sink<Action>) -> BackStackScreen<AnyScreen>.Item {
let title: String
if let authenticationError = error {
title = authenticationError.localizedDescription
@@ -252,7 +252,7 @@ extension AuthenticationWorkflow {
)
return BackStackScreen.Item(
- screen: twoFactorScreen,
+ screen: AnyScreen(twoFactorScreen),
barVisibility: .visible(BackStackScreen.BarContent(
leftItem: BackStackScreen.BarContent.BarButtonItem.button(BackStackScreen.BarContent.Button(
content: .text("Cancel"),

MODIFY
swift/Samples/TicTacToe/Sources/Game/RunGameWorkflow.swift
swift/Samples/TicTacToe/Sources/Game/RunGameWorkflow.swift
@@ -84,19 +84,19 @@ extension RunGameWorkflow {
// MARK: Rendering
extension RunGameWorkflow {
- typealias Rendering = AlertContainerScreen<ModalContainerScreen<BackStackScreen>>
+ typealias Rendering = AlertContainerScreen<ModalContainerScreen<BackStackScreen<AnyScreen>>>
func render(state: RunGameWorkflow.State, context: RenderContext<RunGameWorkflow>) -> Rendering {
let sink = context.makeSink(of: Action.self)
var modals: [ModalContainerScreenModal] = []
var alert: Alert?
- var backStackItems: [BackStackScreen.Item] = [BackStackScreen.Item(
- screen: newGameScreen(
+ var backStackItems: [BackStackScreen<AnyScreen>.Item] = [BackStackScreen.Item(
+ screen: AnyScreen(newGameScreen(
sink: sink,
playerX: state.playerX,
playerO: state.playerO
- ),
+ )),
barVisibility: .hidden
)]
@@ -111,7 +111,7 @@ extension RunGameWorkflow {
)
.rendered(with: context)
backStackItems.append(BackStackScreen.Item(
- screen: takeTurnsScreen,
+ screen: AnyScreen(takeTurnsScreen),
barVisibility: .visible(BackStackScreen.BarContent(
leftItem: BackStackScreen.BarContent.BarButtonItem.button(BackStackScreen.BarContent.Button(
content: .text("Quit"),
@@ -130,7 +130,7 @@ extension RunGameWorkflow {
)
.rendered(with: context)
backStackItems.append(BackStackScreen.Item(
- screen: takeTurnsScreen,
+ screen: AnyScreen(takeTurnsScreen),
barVisibility: .visible(BackStackScreen.BarContent(
leftItem: BackStackScreen.BarContent.BarButtonItem.button(BackStackScreen.BarContent.Button(
content: .text("Quit"),

MODIFY
swift/Samples/TicTacToe/Sources/Main/MainWorkflow.swift
swift/Samples/TicTacToe/Sources/Main/MainWorkflow.swift
@@ -65,7 +65,7 @@ extension MainWorkflow {
// MARK: Rendering
extension MainWorkflow {
- typealias Rendering = AlertContainerScreen<ModalContainerScreen<BackStackScreen>>
+ typealias Rendering = AlertContainerScreen<ModalContainerScreen<BackStackScreen<AnyScreen>>>
func render(state: MainWorkflow.State, context: RenderContext<MainWorkflow>) -> Rendering {
switch state {

Make BackStackScreen generic

Part of #1046