refactor(iOS, FormSheet v5): Move AppearanceCoordinator/Applicator to ContentController#4074
Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors the iOS FormSheet (v5) implementation to align with the Split host pattern: the host component now only records invalidation flags on prop/window changes, and the content controller owns the appearance coordinator/applicator and performs the batched updates after React mounting transactions complete.
Changes:
- Moved
RNSFormSheetAppearanceCoordinator/RNSFormSheetAppearanceApplicatorownership and update execution intoRNSFormSheetContentController. - Added invalidation flags on
RNSFormSheetHostComponentViewand forwarded them to the controller infinalizeUpdates/didMoveToWindow. - Wired mounting-transaction callbacks from the host to the controller so the controller flushes pending appearance/presentation work in
mountingTransactionDidMount.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| ios/gamma/modals/form-sheet/RNSFormSheetHostComponentView.mm | Replaces direct appearance/presentation updates with invalidation flags + controller signals, and forwards mounting-transaction callbacks. |
| ios/gamma/modals/form-sheet/RNSFormSheetHostComponentView.h | Exposes isOpen to allow the controller to derive presentation state from host props. |
| ios/gamma/modals/form-sheet/RNSFormSheetContentController.mm | Takes over appearance coordinator/applicator and executes pending appearance/presentation updates after mounting transactions. |
| ios/gamma/modals/form-sheet/RNSFormSheetContentController.h | Adds controller “signal” APIs, mounting-transaction hooks, and a weak back-reference to the host view. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
kkafar
left a comment
There was a problem hiding this comment.
I have few remarks. Please answer them before proceeding.
| - (void)reactMountingTransactionWillMount; | ||
| - (void)reactMountingTransactionDidMount; |
There was a problem hiding this comment.
It was my mistake to name these like this.
The content controller should ideally not be aware of the react inner workings.
We also likely don't need will callback.
Single flushPendingUpdates should be enough. Just as it is done in tabs.
| if (_needsInitialDetentReset) { | ||
| _needsInitialDetentReset = NO; | ||
| [_appearanceApplicator resetInitialDetent]; | ||
| } |
There was a problem hiding this comment.
I do my first reading & don't understand the code fully yet, but detent update in appearance method doesn't fit for me. Aren't detents rather a behaviour?
Thinking now. Maybe call it configuration or something and then have both detents and appearance here.
There was a problem hiding this comment.
you're right, there are a few things I don't like here:
- appearance & behavior separation
- that AppearanceApplicator is not stateless
my current plan is to:
- separate appearance and behavior on Providers level for this PR
- refactor AppearanceApplicator to extract Behavior configuration (next PR)
- make AppearanceApplcator stateless (next PR)
let me know if you're okay with that
|
|
||
| #pragma mark - Appearance | ||
|
|
||
| - (void)updateAppearanceIfNeededForHost:(nonnull RNSFormSheetHostComponentView *)host |
There was a problem hiding this comment.
...ForHost suggests that you apply the appearance configuration on the host object, which is not the case. I'd name it ...withConfigProvider or somethig like this.
This method ideally, shouldn't take RNSFormSheetHostComponentView, but rather an interface of a delegate / provider. This is not a priority though.
| // Invalidation flags | ||
| BOOL _needsPresentationUpdate; | ||
| BOOL _needsAppearanceUpdate; | ||
| BOOL _needsInitialDetentReset; |
There was a problem hiding this comment.
Should these live on the host?
All the invalidation flags should leave on the content controller. Take a look at how Tabs are implemented. Host & Screens report the need (directly or not) of the update to the controller & it holds it, and clears the flag when it runs the update logic.
We can talk about it some more if that's not fully clear.
| }]; | ||
| if (_needsPresentationUpdate) { | ||
| _needsPresentationUpdate = NO; | ||
| [_controller setNeedsPresentationUpdate]; |
There was a problem hiding this comment.
Instead of holding these additional flags on the host and passing this information to the controller here we can directly signalise to the controller when we e.g. detect prop change.
| controller:(RNSFormSheetContentController *)controller | ||
| coordinator:(RNSFormSheetAppearanceCoordinator *)coordinator; | ||
| - (void)updateAppearanceIfNeededForAppearanceProvider:(id<RNSFormSheetAppearanceProvider>)appearanceProvider | ||
| behaviorProvider:(id<RNSFormSheetBehaviorProvider>)behaviorProvider |
There was a problem hiding this comment.
note: behavior will have a dedicated path, but I'm extracting it to a separate PR in which I'm going to introduce a dedicated class for that
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
kkafar
left a comment
There was a problem hiding this comment.
Looks good. I still have few remarks / questions. Let's answer them.
| - (void)updateAppearanceIfNeededForHost:(RNSFormSheetHostComponentView *)host | ||
| controller:(RNSFormSheetContentController *)controller | ||
| coordinator:(RNSFormSheetAppearanceCoordinator *)coordinator; | ||
| - (void)updateAppearanceIfNeededForAppearanceProvider:(id<RNSFormSheetAppearanceProvider>)appearanceProvider |
There was a problem hiding this comment.
I have one remark regarding naming.
| - (void)updateAppearanceIfNeededForAppearanceProvider:(id<RNSFormSheetAppearanceProvider>)appearanceProvider | |
| - (void)updateAppearanceIfNeededWithAppearanceProvider:(id<RNSFormSheetAppearanceProvider>)appearanceProvider |
The For part does sound inappropriate and misleading in this context.
| - (nullable UIWindow *)hostWindow | ||
| { | ||
| return self.window; | ||
| } |
There was a problem hiding this comment.
I don't understand what hostWindow is for. The window is shared for both host & content hierarchies. Why do we need additional method here? Do you need window earlier than onWillMoveToWindow on RNSFormSheetContentController.view?
There was a problem hiding this comment.
When I navigate to the screen that has FormSheet with initially isOpen=true set, what I'm observing is that on the 1st mounting transaction, flushPendingUpdates updates the presentation and calls updatePresentationState before willMoveToWindow is called on ContentView
There was a problem hiding this comment.
Okay, but why do you even need a window for before you receive willMoveToWindow?
There was a problem hiding this comment.
updatePresentationState triggers presentFromWindow and in that method I'm searching for a VC that will present this ContentController
There was a problem hiding this comment.
Okay. I don't like the approach that RNSFormSheetContentController is responsible for its own presentation, however it's not crucial atm, it's more important to progress.
Let's create a ticket to clean this up.
Host should be responsible for presentation of the content controller and the logic should leave there.
There was a problem hiding this comment.
| - (BOOL)prefersGrabberVisible | ||
| { | ||
| return _prefersGrabberVisible; | ||
| } | ||
|
|
||
| - (CGFloat)preferredCornerRadius | ||
| { | ||
| return _preferredCornerRadius; | ||
| } | ||
|
|
||
| - (NSInteger)largestUndimmedDetentIndex | ||
| { | ||
| return _largestUndimmedDetentIndex; | ||
| } |
There was a problem hiding this comment.
Are these necessary? You have declared properites on RNSFormSheetHostComponentView.h, aren't they synthesized by the compiler?
There was a problem hiding this comment.
dc3f9ac
note: having these providers, I think that we shouldn't expose properties from HostComponent at all, but rather operate on proper providers
There was a problem hiding this comment.
Sorry, I think I didn't communicate clearly.
These properties SHOULD exist on RNSFormSheetHostComponentView interface. We always expose the props.
My remark is that you don't have to write their getters in .mm file. IIRC the getters should be synthesized by the compiler.
There was a problem hiding this comment.
correct, these getters will be synthesized, okay will expose props
There was a problem hiding this comment.
Let's fix the code then. Restore the properties on the interface and remove unnecessary implementations.
This reverts commit dc3f9ac.
## Description A follow-up for #4074 . It addresses the overloaded "Appearance" concept by logically separating visual updates from behavioral updates. Previously, both visual props (e.g. grabber visibility) and structural props (e.g. detents) were grouped together under a single "Appearance" applicator. This PR introduces a more granular UpdateCoordinator and separates the logic. ## Changes - Renamed `RNSFormSheetAppearanceCoordinator` to `RNSFormSheetUpdateCoordinator`. - Replaced old flags with `RNSFormSheetUpdateFlags`, having distinct Appearance, Behavior and Presentation flags. - Replaced `RNSFormSheetAppearanceApplicator` with `RNSFormSheetConfigurationApplicator` that processes both Appearance and Behavior providers and applies them inside a single block. - Refactored RNSFormSheetContentController to separate `updateConfigurationIfNeeded` from `updatePresentationIfNeeded`. `flushPendingUpdates` now configures the sheet first, then presents it. ## Before & after - visual documentation N/A - refactor ## Test plan Tested with existing SFTs for FormSheet. ## Checklist - [ ] Included code example that can be used to test this change. - [ ] For visual changes, included screenshots / GIFs / recordings documenting the change. - [ ] For API changes, updated relevant public types. - [ ] Ensured that CI passes
Description
Previously, there was a tight coupling where
RNSFormSheetContentControllerandRNSFormSheetHostComponentViewhad a direct dependency. By introducing dedicated provider protocols, the UIKit is now completely agnostic of React.Closes: https://github.com/software-mansion/react-native-screens-labs/issues/1454
Changes
RNSFormSheetHostComponentViewdependencies fromRNSFormSheetContentControllerandRNSFormSheetAppearanceApplicatorBefore & after - visual documentation
N/A - refactor
Test plan
No regression in existing examples
Checklist