Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,45 @@ public struct ViewControllerDescription {
}
}

/// Constructs a view controller description by providing closures used to
/// build and update a specific view controller type. This initializer allows
/// clients to configure which types of UIViewControllers the description can
/// use for updating.
///
/// - Parameters:
/// - performInitialUpdate: If an initial call to `update(viewController:)`
/// will be performed when the view controller is created. Defaults to `true`.
///
/// - environment: The `ViewEnvironment` that should be injected above the
/// described view controller for ViewEnvironmentUI environment propagation.
/// This is typically passed in from a `Screen` in its
/// `viewControllerDescription(environment:)` method.
///
/// - build: Closure that produces a new instance of the view controller.
///
/// - canUpdate: Closure that controls when `update` can succssfully update the
/// given view controller. This can be useful for inspecting the dynamic type
/// of the given view controller.
///
/// - update: Closure that updates the given view controller.
public init(
performInitialUpdate: Bool = true,
environment: ViewEnvironment,
build: @escaping () -> UIViewController,
canUpdate: @escaping (UIViewController) -> Bool,
update: @escaping (UIViewController) -> Void
) {
self.performInitialUpdate = performInitialUpdate

self.kind = .init(UIViewController.self, checkViewControllerType: canUpdate)

self.environment = environment

self.build = build

self.update = update
}

/// Construct and update a new view controller as described by this view controller description.
/// The view controller will be updated before it is returned, so it is fully configured and prepared for display.
public func buildViewController() -> UIViewController {
Expand Down Expand Up @@ -191,10 +230,9 @@ extension ViewControllerDescription {
private let checkViewControllerType: (UIViewController) -> Bool

/// Creates a new kind for the given view controller type.
public init<VC: UIViewController>(_ kind: VC.Type) {
public init<VC: UIViewController>(_ kind: VC.Type, checkViewControllerType: @escaping ((UIViewController) -> Bool) = { $0 is VC }) {
self.viewControllerType = VC.self

self.checkViewControllerType = { $0 is VC }
self.checkViewControllerType = checkViewControllerType
}

/// If the given view controller is of the correct type to be updated by this view controller description.
Expand Down
54 changes: 54 additions & 0 deletions WorkflowUI/Tests/ViewControllerDescriptionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import Workflow
@testable import WorkflowUI

fileprivate class BlankViewController: UIViewController {}
fileprivate class BlankViewControllerSubclass: BlankViewController {}
fileprivate class SecondBlankViewController: UIViewController {}

@objc fileprivate protocol MyProtocol {
func update()
Expand Down Expand Up @@ -203,6 +205,58 @@ class ViewControllerDescriptionTests: XCTestCase {
let viewControllerAgain = description.buildViewController()
XCTAssertFalse(viewController === viewControllerAgain)
}

func test_viewControllerTypes() {
func polymorphicController() -> UIViewController { BlankViewController() }
let controller = polymorphicController()

// Case 1: Static types losing granularity when supplying a superclass type to the standard init.
// kind.viewControllerType == UIViewController.self.
let descriptionWithStaticSupertype = ViewControllerDescription(
type: type(of: controller),
environment: .empty,
build: { controller },
update: { _ in }
)
// canUpdate(viewController:) evaluates to true for any UIViewController type.
XCTAssertTrue(descriptionWithStaticSupertype.canUpdate(viewController: polymorphicController()))
XCTAssertTrue(descriptionWithStaticSupertype.canUpdate(viewController: BlankViewController()))
XCTAssertTrue(descriptionWithStaticSupertype.canUpdate(viewController: UIViewController()))
XCTAssertTrue(descriptionWithStaticSupertype.canUpdate(viewController: SecondBlankViewController()))
XCTAssertTrue(descriptionWithStaticSupertype.canUpdate(viewController: BlankViewControllerSubclass()))

// Case 2: Using canUpdate to inspect dynamic types so that updating can function as expected.
// kind.viewControllerType == UIViewController.self.
let descriptionWithDynamicTypeInspection = ViewControllerDescription(
environment: .empty,
build: { controller },
canUpdate: { viewController in
viewController.isKind(of: type(of: controller))
},
update: { _ in }
)
// canUpdate(viewController:) will evaluate to true for matching UIViewController types or subclasses.
XCTAssertTrue(descriptionWithDynamicTypeInspection.canUpdate(viewController: polymorphicController()))
XCTAssertTrue(descriptionWithDynamicTypeInspection.canUpdate(viewController: BlankViewController()))
XCTAssertFalse(descriptionWithDynamicTypeInspection.canUpdate(viewController: UIViewController()))
XCTAssertFalse(descriptionWithDynamicTypeInspection.canUpdate(viewController: SecondBlankViewController()))
XCTAssertTrue(descriptionWithDynamicTypeInspection.canUpdate(viewController: BlankViewControllerSubclass()))

// Case 3: Use case of the standard init using static types.
// kind.viewControllerType == BlankViewController.self.
let descriptionWithStaticSubtype = ViewControllerDescription(
type: BlankViewController.self,
environment: .empty,
build: { BlankViewController() },
update: { _ in }
)
// canUpdate(viewController:) will evaluate to true for matching UIViewController types or subclasses.
XCTAssertTrue(descriptionWithStaticSubtype.canUpdate(viewController: polymorphicController()))
XCTAssertTrue(descriptionWithStaticSubtype.canUpdate(viewController: BlankViewController()))
XCTAssertFalse(descriptionWithStaticSubtype.canUpdate(viewController: UIViewController()))
XCTAssertFalse(descriptionWithStaticSubtype.canUpdate(viewController: SecondBlankViewController()))
XCTAssertTrue(descriptionWithStaticSubtype.canUpdate(viewController: BlankViewControllerSubclass()))
}
}

class ViewControllerDescription_KindIdentifierTests: XCTestCase {
Expand Down
Loading