Skip to content

Commit 028c47c

Browse files
authored
fix(iOS, Tabs): bottom accessory opacity on appearance change (#3467)
## Description Fixes bottom accessory content view switching workaround on appearance change. | before | after | | --- | --- | | <video src="https://github.com/user-attachments/assets/9a8c15ec-b140-4f90-b0f6-1bb1af2f5657" /> | <video src="https://github.com/user-attachments/assets/0630273c-e412-4fcb-ac6a-287a6dba668d" /> | Thanks to @Ubax for reporting the bug. ### Reasoning For RN >= 0.82, bottom accessory uses "content view switching"/"double-rendering" workaround (more details in PR description of #3288). Two bottom accessories are rendered by JS - one for regular environment, one for inline environment and both are inserted to bottom accessory view on the native side. To control which version is visible, we change `view.layer.opacity` depending on current environment. Opacity is also controlled by `RCTViewComponentView` in `invalidateLayer` method. It is called in 2 places: - `finalizeUpdates`, - `traitCollectionDidChange:`. `invalidateLayer` overrides our custom opacity, that's why we update it by calling `handleContentViewVisibilityForEnvironmentIfNeeded` in `RNSBottomAccessoryContentComponentView`'s `finalizeUpdates` after `super` implementation is called. The same was missing for `traitCollectionDidChange`. The layer invalidation is called when `[self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]` (=> when appearance changes). [`traitCollectionDidChange`](https://developer.apple.com/documentation/uikit/uitraitenvironment/traitcollectiondidchange(_:)?language=objc) is a deprecated API for handling trait changes. For detecting current bottom accessory environment, we use [`registerForTraitChanges:withHandler:`](https://developer.apple.com/documentation/uikit/uitraitchangeobservable-7qoet/registerfortraitchanges:withhandler:?language=objc). Unfortunately, we can't use new API for updating opacity as `traitCollectionDidChange` is called after handlers registered via `registerForTraitChanges:withHandler:`. In the future, if `RCTViewComponentView` switches to new API, we will need to adapt as well. Here is some research that might become useful: [`hasDifferentColorAppearanceComparedToTraitCollection:`](https://developer.apple.com/documentation/uikit/uitraitcollection/hasdifferentcolorappearance(comparedto:)?language=objc) can be replaced with new API and [`systemTraitsAffectingColorAppearance`](https://developer.apple.com/documentation/uikit/uitraitcollection/systemtraitsaffectingcolorappearance-18zhm?language=objc) but this API is available on iOS 17+, so we'll need to fallback to hardcoded traits array. Information about which traits affect color appearance can be found in in-code docs for `hasDifferentColorAppearanceComparedToTraitCollection`: > Currently, a change in any of these traits could affect dynamic colors: > `userInterfaceIdiom`, `userInterfaceStyle`, `displayGamut`, `accessibilityContrast`, `userInterfaceLevel` > and more could be added in the future. We can also research whether it would be possible to update opacity style/prop directly in ShadowNode instead of only native side. There is an existing ticket to research this: software-mansion/react-native-screens-labs#558. ## Changes - update opacity after `RCTViewComponentView` invalidates layer on appearance change ## Test code and steps to reproduce Run `Test3288`. You can modify bottom accessory by changing prop passed to `BottomTabsContainer` to: ```tsx bottomAccessory={env => { if (env === 'inline') { return <Text style={{ marginLeft: 40 }}>Inline</Text>; } return ( <Text style={{ textAlign: 'right', marginRight: 40 }}> Not Inline </Text> ); }} ``` ## Checklist - [x] Included code example that can be used to test this change - [x] Ensured that CI passes
1 parent 5463180 commit 028c47c

File tree

1 file changed

+13
-0
lines changed

1 file changed

+13
-0
lines changed

ios/bottom-tabs/accessory/RNSBottomTabsAccessoryContentComponentView.mm

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,19 @@ - (void)didMoveToWindow
4040
}
4141
}
4242

43+
// `RCTViewComponentView` uses this deprecated callback to invalidate layer when trait collection
44+
// `hasDifferentColorAppearanceComparedToTraitCollection`. This updates opacity which breaks our
45+
// content view switching workaround. To mitigate this, we update content view visibility after
46+
// RCTViewComponentView handles the change. We need to use the same deprecated callback as it's
47+
// called after callbacks registered via the new API.
48+
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection
49+
{
50+
[super traitCollectionDidChange:previousTraitCollection];
51+
if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {
52+
[_accessoryView.helper handleContentViewVisibilityForEnvironmentIfNeeded];
53+
}
54+
}
55+
4356
#endif // RNS_BOTTOM_ACCESSORY_AVAILABLE && REACT_NATIVE_VERSION_MINOR >= 82
4457

4558
#if RCT_NEW_ARCH_ENABLED

0 commit comments

Comments
 (0)