-
Notifications
You must be signed in to change notification settings - Fork 47
[feat]: add ViewEnvironmentUI module #205
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
d33bf2e
to
5121949
Compare
5121949
to
b37dad6
Compare
|
||
needsEnvironmentUpdate = false | ||
|
||
if let observing = self as? ViewEnvironmentObserving { |
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.
Do you need this if let
, given the chain upwards of the protocol inheritance?
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, this protocol does not inherit from ViewEnvironmentObserving
.
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.
Nor does ViewEnvironmentPropagatingObject
.
public func addEnvironmentNeedsUpdateObserver( | ||
_ onNeedsUpdate: @escaping (ViewEnvironment) -> Void | ||
) -> ViewEnvironmentUpdateObservationLifetime { | ||
let object = NSObject() |
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.
Thought: Is it worth making a special type / subclass, so the memory debugger, etc, shows the type more obviously in case something goes wrong?
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.
We can!
|| environmentAncestorOverride == nil, | ||
"Attempting to set environment ancestor override when one is already set." | ||
) | ||
objc_setAssociatedObject(self, &AssociatedKeys.ancestorOverride, newValue, .OBJC_ASSOCIATION_RETAIN) |
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.
Trying to think through if we can get into any weird cases by making this a retain, vs a weak.
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.
The type here is a closure so it'd be really easy to mess that up I think...
The fact that this property is retained is part of the trick I used in the prototype to add support for inserting a propagation node above the view controller being described in a ViewControllerDescription
—this closure is the only thing holding a reference to that fake node which provides the bridged 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.
Couple questions; but approving since a lot of this has already existed in Market for the most part, and has been reviewed.
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.
Mostly tweaks to docs.
A couple thoughts on potential follow-ups:
- we should mob on diagrams for this some time
- might be nice to have a convenience function for inserting a customization node between existing ancestors & descendants
/// `cancel()` is called on it. | ||
/// | ||
@_spi(ViewEnvironmentWiring) | ||
public func addEnvironmentNeedsUpdateObserver( |
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.
Do we still have a use case for this?
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.
I'm still using it in the prototype inside of TrampolineModalListObserver
.
I think we may actually want to be using this method of observation instead of overriding environmentDescendantsOverride
when the logic making this observation is not owned by the view controller itself.
For example, I'm currently playing with having WorkflowHostingController
set its environmentDescendantsOverride
to an empty array to prevent vanilla propagation invalidations causing a re-render of it's owned rendering
since we'll already be performing a re-render on environmentDidChange
. In our TrampolineModalPresenter
, we need some way to observe invalidations triggered by its owner
, but that owner could be a WorkflowHostingController
. Rather than trying to override environmentDescendantOverrides
and having to compose the existing override (similar to calling super
), it might be cleaner to consider environmentDescendantsOverride
something that only the VC it's applied to should override, and if you need to hook into invalidations, you use the addEnvironmentNeedsUpdateObserver(...)
.
The composing approach does seem to work, but seems like it could get hard to follow when debugging and potentially difficult to document, but I'd be open to utilizing that approach for cases like this if we'd rather consolidate the API a bit.
…ly function doc comments
Co-authored-by:Andrew Watt <[email protected]>
Definitely down to pair on both of these at some point in the near future! |
29f0a80
to
c1241f6
Compare
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.
Thanks for the careful organization of this PR. That and the context in the linked proposal made it a pleasure to review.
My comments are all nits and doc fixes. I wonder if there is some way we can avoid the overloading of the term "ancestor" in our terminology.
/// Customizes the `ViewEnvironment` as it flows through this propagation node to provide overrides to environment | ||
/// values. These changes will be propagated to all descendant nodes. |
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.
nit, orthogonal to this change: Do the customizations in self.customize(environment:)
affect the value passed to self.apply(environment:)
, or only to decendants' apply(environment:)
?
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.
Customizations currently apply to the environment on the node itself. I'll clarify this in documentation.
- (8d4478f) Add note about customizations applying to the node itself
Andrew mentioned that this was surprising during his review and I have a note to follow up on this. Which shape is most intuitive to you?
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.
Good question. I did guess correctly that customizations do apply to self, but I was on the fence about it.
Applying customizations to descendants only would be sensible in that the node can easily opt into applying the same customizations to itself within self.apply(environment:)
. But if nodes almost always want that, then maybe it's annoying to have to start apply(environment:)
with
var environment = environment
customize(environment: environment)
/// If the value of the ancestor is `nil`, by default, ancestors will not call notify this node of needing an | ||
/// environment update as it changes. This allows a node to effectively act as a root node when needed (e.g. | ||
/// bridging from other propagation systems like WorkflowUI). |
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.
ancestors will not
callnotify
As a newbie to view environment propagation, the ancestor/descendant terminology is confusing to me because:
- "Ancestor" describes something that is analogous to a "parent" in other systems. In those other systems, "ancestor" refers to any node that can be reached by calling
parent
recursively - Ditto for "descendant"/"child"
- The documentation sometimes refers to A as ancestor of B if A is in B's
environmentDescendants
, like in this sentence. In other places, the doc uses "ancestor" to meanenvironmentAncestor
, which is now a distinct concept.
Am I misunderstanding anything there?
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, we should probably be more careful with the phrasing here.
As you stated, "ancestor" is any parent (recursively), and "descendant" is children (recursively). A node's ancestor may not be that ancestor nodes' descendant and vice versa. I think this doc comment should be something like:
If the value of the ancestor is
nil
, by default, other nodes configured with this node as a descendant will not notify this node of needing an environment update as it changes. This allows a node to effectively act as a root node when needed (e.g. bridging from other propagation systems like WorkflowUI).
Does that make sense?
- (6802cb5 ) Improve documentation around the difference between ancestor and a node configured with the node in question as a descendant
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 minor suggestions!
Overview
Adds a new
ViewEnvironmentUI
module which handles propagation ofViewEnvironment
throughUIViewController
,UIView
, and any arbitrary object node.This PR is part of the larger effort to add support for automatic
ViewEnvironment
bridging betweenUIKit
propagation andWorkflowUI
propagation withinWorkflowUI
.See the associated proposal for more information.
Changes
The changes in this PR are most easily reviewable by commit:
Port ViewEnvironmentUI from Market and add to Development.podspec (6eacf4a)
This is essentially a copy paste of the framework which currently exists in MarketUI, but with a few typos in documentation fixed and added license header docs.
Simplify protocol hierarchy (7b7c59f)
Removes the
ViewEnvironmentCustomizing
protocol and merges the functions it defined intoViewEnvironmentObserving
.Adds default implementations of all functions on
ViewEnvironmentObserving
so you can pick and chose how you'd like to observe without requiring an empty implementation for functions you may not be concerned with.Removes the ability to implement
var viewEnvironment: ViewEnvironment
yourself. This was useful when bridging between propagation contexts and/or when a node wanted to act as a root without pulling the environment through all ancestors above it. Instead, setting the nodesenvironmentAncestor
tonil
will prevent the pull from above and will prevent other nodes who have this node set as an ancestor from triggeringsetNeedsEnvironmentUpdate
on the node.Rename viewEnvironment to environment (593250d)
This rename will cause ambiguity when using
ScreenViewController
ifViewEnvironmentUI
is imported since that superclass already has anenvironment
property. This will be resolved in a future PR targeting the feature branch.Add environmentDidChange observation option (b37dad6)
Adds a hook that observers can utilize to respond aggressively to environment changes rather than needing to wait until the
setNeedsEnvironmentUpdate
/apply(environment:)
dance has completed (which is usually deferred since it's backed bysetNeedsLayout
/viewWillLayoutSubviews
).Checklist