-
Notifications
You must be signed in to change notification settings - Fork 47
[feat]: add imperative environment customization support #231
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
Conversation
8810208
to
c67b999
Compare
@@ -351,17 +380,21 @@ public final class ViewEnvironmentUpdateObservationLifetime { | |||
/// This is called in `deinit`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this still true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yeah I should add some nuance to this comment. The closure provided for onRemove
is only called in deinit if it was not called manually.
- (7242f1f) Improve doc comment regarding
remove()
being called indeinit
@@ -351,17 +380,21 @@ public final class ViewEnvironmentUpdateObservationLifetime { | |||
/// This is called in `deinit`. | |||
/// | |||
public func remove() { | |||
guard let onRemove else { | |||
preconditionFailure("Environment customization was already removed") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems like an assert warning about unexpected behavior may be more appropriate. this is idempotent, right (other than the precondition)? will something break horribly if it's called more than once?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Asserting is fine with me. I think the idea here is that we consider it a programmer error to try to remove a customization more than once, even if it is effectively idempotent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh whoops, I added this to the wrong lifetime 🤦
- (39d493d) Apply removal assertion changes to customization lifetime
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this all looks reasonable to me!
/// until `remove()` is called on it. | ||
/// | ||
@_spi(ViewEnvironmentWiring) | ||
public func addEnvironmentCustomization( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just to better my own understanding – should this trigger the 'setNeedsEnvironmentUpdate' (or whatever its name is) logic, or does that responsibility lie at a different level?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah—I'm fairly torn on whether or not this should automatically call setNeedsEnvironmentUpdate()
on your behalf. You'll almost always want to call it after adding a customization, but I figured there may be cases where you'd prefer to defer that call or manually manage when the invalidation occurs (e.g. for performance reasons). setNeedsEnvironmentUpdate()
does trigger environmentDidChange()
calls which are not deferred to the next layout cycle, and we use this to perform updates in some cases within WorkflowUI and Market. For example, WorkflowHostingController will re-render its content whenever environmentDidChange()
is called.
The current shape also makes it a bit easier to ensure events happen at more appropriate times. For example, if you were to be adding a customization to a new child VC in a container VC, you might have something like this:
addChild(childVC)
customizationLifetime = childVC
.addEnvironmentCustomization(makeCustomization(for: column))
if let view = viewIfLoaded {
view.add(childVC.view)
}
childVC.didMove(toParent: self)
childVC.setNeedsEnvironmentUpdate()
This order of operations means:
- The child VC is added as a child and the customization is added before the child VC's view is (conditionally) loaded. This ensures any
environment
access duringviewDidLoad
would have the appropriate environment wiring path and customizations. setNeedsEnvironmentUpdate()
is called after the parent/child relationship is fully established (didMove(toParent:)
has been called). I'm not aware of any real issues that could come from calling this beforedidMove(toParent:)
, but it seems like the most appropriate time in the VC lifecycle IMO.
I'm very open to automatically calling setNeedsEnvironmentUpdate()
automatically when a customization is added if we prefer the safety here!
@@ -119,6 +119,10 @@ extension ViewEnvironmentPropagating { | |||
public var environment: ViewEnvironment { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
more of a general question about the changeset – is this alternate API mainly for convenience? curious what the motivating examples are (if they exist).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's mainly for convenience. The motivating example is support for what we call "pane-based size class", where a split container VC is able to override the horizontal size class for a particular pane based on the available horizontal width of that pane.
Without this change we'd either need to insert a node between the split container VC and it's children, or introduce a wrapper VC around those children to perform the environment mutation.
Overview
Adds support for declaring environment customizations to a
ViewEnvironmentPropagatingNode
without subclassing, custom wiring, or VC containment.Kudos to @watt for the idea!
Checklist