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

[swift] Remove view registry #974

Merged
merged 2 commits into from Feb 26, 2020
Merged

[swift] Remove view registry #974

merged 2 commits into from Feb 26, 2020

Conversation

bencochran
Copy link
Collaborator

@bencochran bencochran commented Feb 17, 2020

This removes the view registry in favor of having screens provide their own view controllers (via ViewControllerDescription).

This improves compile-time safety as well as allows for easier container types (we can now have generic screen types, and pass-through containers no longer must provide a wrapper view
controller).

Closes #815

private lazy var trailingContainerView: ContainerView = .init()

private var currentRatio: CGFloat {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

got rid of these redundant copies of the values while I was in here


update(with: screen)
}
fileprivate final class LoginViewController: UIViewController {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Made this one not be a subclass of ScreenViewController just to show that is’s not necessary as a superclass

@@ -19,51 +19,60 @@
import UIKit


internal final class AnyScreenViewController: ScreenViewController<AnyScreen> {
public final class DescribedViewController: UIViewController {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This isn’t a rename, but a rewrite. I suppose they do perform mostly the same job, though.

@@ -19,24 +19,22 @@
import UIKit

public struct AnyScreen: Screen {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Happy to see this collapse into just a passthrough for the view controller description

internal func buildViewController() -> UIViewController {
let viewController = build()
assert(canUpdate(viewController: viewController), "View controller description built a view controller it cannot update (\(viewController) is not exactly type \(viewControllerType))")
return viewController
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I initially considered calling update with the view controller after building it. Technically that’s redundant for our ScreenViewController subclasses (since they all take the screen in in init), but I’m worried that omitting it will cause a lot of people to initially build a view controller that’s not fully populated without realizing it. Anyone opposed to calling update eagerly here?

Copy link
Member

Choose a reason for hiding this comment

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

could that cause an issue if an update implementation expects the view controller to be inside a window?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

canUpdate is just the type check, the update implementation doesn’t play in here. Though, if someone wrote an update method that required being in a window, I’d be very sad at them.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh, I totally misattributed your question and thought it was about the assert(canUpdate…. So, yeah doing an update eagerly here would break a view controller’s ability to assume it’s in a window when it gets updated. But I strongly believe that’s not an assumption we should allow people to make.

This removes the view registry in favor of having screens provide
their own view controllers (via `ViewControllerDescription`).

This improves compile-time safety as well as allows for easier
container types (we can now have generic screen types, and
pass-through containers no longer must provide a wrapper view
controller).
@bencochran bencochran changed the title [WIP] [swift] Remove view registry [swift] Remove view registry Feb 18, 2020
/// - build: Closure that produces a new instance of the view controller
/// - update: Closure that updates the given view controller
public init<VC: UIViewController>(type: VC.Type = VC.self, build: @escaping () -> VC, update: @escaping (VC) -> Void) {
self.viewControllerType = type
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of doing this, what if we hold-on to the canUpdate block?

self.canUpdate = { untypedVC in
  return untypedVC is VC
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I played with that locally, but found it really useful for debugging to have the type around in a printable manner. I also don’t think we want is because we only want exact matches, not subclasses IMO.

Copy link
Member

Choose a reason for hiding this comment

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

curious why we wouldn't want to allow subclasses in this context (out of ignorance, I'm not familiar enough yet to know why or why not to allow it :) )

I could see a use case for something like subclasses altering layout of the same views, and using the same screen to update the super class and the subclass

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Since a single view controller description is responsible for building and updating, I can’t imagine a scenario where you’d reasonably build a type of a superclass and then be passed a value of a subclass, except by mistake.

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we actually want to be able to pass in something other than the default (VC.self) here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In the case of the extension on ScreenViewController, it was inferring a type of ScreenViewController<FooScreen> instead of FooViewController with older Swift versions (where I couldn’t do Self(screen: screen) and instead had to do self.init(screen: screen). Adding this hinting allowed me to fix that.

Copy link
Contributor

@dhavalshreyas dhavalshreyas left a comment

Choose a reason for hiding this comment

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

This is awesome, thank you!

Copy link
Contributor

@JustinDSN JustinDSN left a comment

Choose a reason for hiding this comment

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

This is really really great! Thank you! Just left a few comments.

@AquaGeek
Copy link
Contributor

Cases where we want to change the resulting view controller type (e.g. adapt a Screen defined elsewhere to our use case) now require you to wrap that in your own Screen type (or pull off the various properties that you need/care about), right?

Also, do we care that this requires that we use view controllers as the currency globally (as opposed to e.g. views)?

@bencochran
Copy link
Collaborator Author

Cases where we want to change the resulting view controller type (e.g. adapt a Screen defined elsewhere to our use case) now require you to wrap that in your own Screen type (or pull off the various properties that you need/care about), right?

Yep, that’s the intended pattern should we need it. It would also be valid for a workflow to render a type that’s not a Screen then have a consumer conform it’s type to Screen to provide a view controller, though I like that approach less.

Also, do we care that this requires that we use view controllers as the currency globally (as opposed to e.g. views)?

Not personally. It’s already the case today with view registry. We could provide a pretty easy convenience for ViewControllerDescription that describes a UIView if we wanted.

Copy link
Collaborator

@timdonnelly timdonnelly left a comment

Choose a reason for hiding this comment

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

Thanks!

Copy link
Collaborator

@zach-klippenstein zach-klippenstein left a comment

Choose a reason for hiding this comment

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

This is awesome, but how bad is updating existing code to use this going to be? Should we leave the ViewRegistry parameters in for now and do a release with support for the new technique, so we can migrate existing code over incrementally?

@dhavalshreyas
Copy link
Contributor

This is awesome, but how bad is updating existing code to use this going to be? Should we leave the ViewRegistry parameters in for now and do a release with support for the new technique, so we can migrate existing code over incrementally?

We tried a few approaches (#816) which would allow us to migrate incrementally, however there were some risks with this approach. Since we'll need the same ViewRegistry to get carried over to the leafs, if we use a non-ViewRegistry Screen in between, there will be a run-time crash.

The hard-removal approach is a lot more up-front work to do the migration, however we'll have compile-time safety and will be a cleaner migration.

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.

[Swift] Proposal: Remove ViewRegistry
7 participants