Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates to make it easier to avoid using DescribedViewController, allowing flattening of VC hierarchies. #153

Merged
merged 1 commit into from
Sep 7, 2022

Conversation

kyleve
Copy link
Contributor

@kyleve kyleve commented Aug 27, 2022

This change makes it easier to avoid using DescribedViewController, which adds a (largely needless) layer to view controller hierarchies. I'll leave comments inline on relevant additions with comments.

For Square folks, I recommend seeing how this is used in context in https://github.com/squareup/market/pull/4559.

Before

image

After

image

Checklist

  • Unit Tests
  • UI Tests
  • Snapshot Tests (iOS only)
  • I have made corresponding changes to the documentation

@kyleve kyleve force-pushed the kve/flatten-described-vcs branch from a76c792 to 8907b6a Compare August 29, 2022 23:12
/// - parameter environment: The `environment` to used when updating the view controller.
/// - parameter onChange: A callback called if the view controller instance changed.
///
public func update<VC: UIViewController, ScreenType: Screen>(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useful for doing one-liner updates for contained view controllers. Eg, in Market we do:

private var content : UIViewController

...

override func screenDidChange(from previousScreen: ModalHostContainer, previousEnvironment: ViewEnvironment) {

    update(child: \.content, with: screen.content, in: environment)

    ...
}

@@ -26,4 +26,10 @@
func viewControllerDescription(environment: ViewEnvironment) -> ViewControllerDescription
}

extension Screen {
public func update(viewController: UIViewController, with environment: ViewEnvironment) {
Copy link
Contributor Author

@kyleve kyleve Aug 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Docs

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used?

It seems easy enough to just call the logic this abstracts, I'd be fine leaving this convenience out 🤷

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used in Market somewhat commonly to avoid having to do the description dance which saves a line of code or so, which IMO is nice, but also fine to remove it.

}
}

public protocol UpdateChildScreenViewController {}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This dance is needed so we can reference Self in the extension method.

/// in a way that is `Equatable` and `Hashable`. When implementing view controller
/// updating and diffing, you can use this type to identify if the backing view controller
/// type changed.
public struct KindIdentifier: Hashable {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this type is Hashable, you can use it to salt your ID types, making handling diffs easier when the underlying type of the VC in the view description changes.

@kyleve kyleve changed the title [WIP DNR] Allow flattening described view controllers out Updates to make it easier to avoid using DescribedViewController, allowing flattening of VC hierarchies. Aug 29, 2022
@kyleve kyleve marked this pull request as ready for review August 29, 2022 23:18
@kyleve kyleve requested a review from mjohnson12 August 30, 2022 12:57
Copy link
Collaborator

@mjohnson12 mjohnson12 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great. I really like the KindIdentifier.

@@ -26,4 +26,10 @@
func viewControllerDescription(environment: ViewEnvironment) -> ViewControllerDescription
}

extension Screen {
public func update(viewController: UIViewController, with environment: ViewEnvironment) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this used?

It seems easy enough to just call the logic this abstracts, I'd be fine leaving this convenience out 🤷


import UIKit

extension UpdateChildScreenViewController where Self: UIViewController {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not put this directly on extension UIViewController and get rid of the protocol?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed because the method references Self, which you can't do in an extension on a concrete type like UIViewController. Blueprint does the same dance, for example.

Comment on lines +79 to +81
parent.isViewLoaded,
old.isViewLoaded,
let container = old.view.superview {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these views were not loaded when this is called, how do we ensure we still insertSubview etc.?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't – we assume the parent view controller will do its own setup in loadView/viewDidLoad when it is asked to load the view.

Comment on lines +46 to +50
/// Describes the `UIViewController` type that backs the `ViewControllerDescription`
/// in a way that is `Equatable` and `Hashable`. When implementing view controller
/// updating and diffing, you can use this type to identify if the backing view controller
/// type changed.
public let kind: KindIdentifier
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

@@ -69,8 +75,10 @@
fatalError("init(coder:) has not been implemented")
}

private func render(screen: ScreenType, environment: ViewEnvironment) {
rootViewController.update(screen: screen, environment: environment)
private func update(screen: ScreenType, environment: ViewEnvironment) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to also call this from viewDidLoad to ensure the views get added etc.?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not – when the container loads its view, it will add the rootVC as a child: https://github.com/square/workflow-swift/blob/main/WorkflowUI/Sources/Container/ContainerViewController.swift#L81


if description.canUpdate(viewController: existing) {
// Easy path: Just update the existing view controller if we can do that.
description.update(viewController: existing)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we potentially need to add subviews etc. here if they're not already added (due to the isViewLoaded checks below)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No – the assumption is the implementation of the view controller will do this in loadView/viewDidLoad, as custom parent VCs are expected to do.

Copy link
Contributor

@jamieQ jamieQ left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changes seem reasonable to me. curious what you think any risks or downsides to this approach may be.

import XCTest
@testable import WorkflowUI

class UIViewControllerExtensionTests: XCTestCase {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests have been added

@kyleve kyleve force-pushed the kve/flatten-described-vcs branch 4 times, most recently from 6771004 to d6704cf Compare September 4, 2022 17:49
…ectly to remove DescribedViewController layering from VC hierarchies.
@kyleve kyleve force-pushed the kve/flatten-described-vcs branch from eff6175 to 7b46aa4 Compare September 6, 2022 23:39
@kyleve kyleve merged commit 14dd4eb into main Sep 7, 2022
@kyleve kyleve deleted the kve/flatten-described-vcs branch September 7, 2022 06:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants