mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
fix(iOS): boxShadow + overflow hidden combination affecting pointer event box-none and scale transform. (#52413)
Summary: This issue is only reproducible with combination of `overflow: hidden` and `boxShadow` i.e. when a [container view](https://github.com/facebook/react-native/blob/2ce7eab5f998788ffa8346b63cc4901d026456ba/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm#L799) is added to the hierarchy. ### Case 1: ```jsx <Pressable pointerEvents="box-none" style={{ width: 300, height: 300, boxShadow: "0 0 100px 0 rgba(0, 0, 0, 0.5)", overflow: "hidden" }} onPress={() => { alert("Tapped"); }} /> ``` Here the view has `box-none` but it will still trigger the press event because this [line](https://github.com/facebook/react-native/blob/2ce7eab5f998788ffa8346b63cc4901d026456ba/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm#L658) returns the `_containerView` as `hitView` and this condition gets [fullfilled](https://github.com/facebook/react-native/blob/2ce7eab5f998788ffa8346b63cc4901d026456ba/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm#L676) (since it is not self). To fix this issue we should iterate on `self.currentContainerView.subviews`. ### Case 2: Transform scale clipping child view. ```jsx <Pressable pointerEvents="box-none" style={{ width: 300, height: 300, overflow: "hidden", transform: [ { scale: 0.5 }, ], boxShadow: "0 0 100px 0 rgba(0, 0, 0, 0.5)", }} onPress={() => { alert("parent view"); }} > <Pressable style={{ position: "absolute", bottom: 0, right: 0, backgroundColor: "blue", height: 50, width: 100, }} onPress={() => { alert("child view"); }} > <Text>Press me</Text> </Pressable> </Pressable>; ``` The above style hides the child view on iOS whereas it is visible on android. | iOS | Android | |---|---| | <img src="https://github.com/user-attachments/assets/14713ec9-1ae7-4425-9348-946d507188e8" width="200px" /> | <img src="https://github.com/user-attachments/assets/0f6e2368-037d-4b03-8a85-8a08f1578ff6" width="200px" /> | This happens because `containerView` uses `self.frame` property which gets mutated due to scale transform. So containerView becomes `actualSize * scale * scale` instead of `actualSize * scale` and ends up clipping child views because of overflow hidden. Please checkout the layout hierarchy in the below screenshot. To fix it we need to use `self.bounds` instead of `self.frame` when setting the frame for container. <img src="https://github.com/user-attachments/assets/9ce41756-f41f-4782-bfc9-b0c281f314b7" width="300px" /> ## Changelog: [IOS] [FIXED] - boxShadow + overflow hidden combination interfering with pointerEvents and transform scale. <!-- Help reviewers and the release process by writing your own changelog entry. Pick one each for the category and type tags: For more details, see: https://reactnative.dev/contributing/changelogs-in-pull-requests Pull Request resolved: https://github.com/facebook/react-native/pull/52413 Test Plan: Test above example snippets and pointer events examples in RNTester. cc - joevilches Reviewed By: cortinico, jorge-cab Differential Revision: D77872110 Pulled By: joevilches fbshipit-source-id: e9693c79b47379531510607921ffaa6d6cc5c17b
This commit is contained in:
committed by
Facebook GitHub Bot
parent
1c5e0ffd9f
commit
c8e5f9766b
+6
-4
@@ -650,7 +650,9 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
|
||||
|
||||
BOOL isPointInside = [self pointInside:point withEvent:event];
|
||||
|
||||
BOOL clipsToBounds = self.currentContainerView.clipsToBounds;
|
||||
UIView *currentContainerView = self.currentContainerView;
|
||||
|
||||
BOOL clipsToBounds = currentContainerView.clipsToBounds;
|
||||
|
||||
clipsToBounds = clipsToBounds || _layoutMetrics.overflowInset == EdgeInsets{};
|
||||
|
||||
@@ -658,8 +660,8 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
|
||||
return nil;
|
||||
}
|
||||
|
||||
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
|
||||
UIView *hitView = [subview hitTest:[subview convertPoint:point fromView:self] withEvent:event];
|
||||
for (UIView *subview = nullptr in [currentContainerView.subviews reverseObjectEnumerator]) {
|
||||
UIView *hitView = [subview hitTest:[subview convertPoint:point fromView:currentContainerView] withEvent:event];
|
||||
if (hitView) {
|
||||
return hitView;
|
||||
}
|
||||
@@ -859,7 +861,7 @@ static RCTBorderStyle RCTBorderStyleFromOutlineStyle(OutlineStyle outlineStyle)
|
||||
|
||||
if (_useCustomContainerView) {
|
||||
if (!_containerView) {
|
||||
_containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
|
||||
_containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)];
|
||||
for (UIView *subview = nullptr in effectiveContentView.subviews) {
|
||||
[_containerView addSubview:subview];
|
||||
}
|
||||
|
||||
@@ -568,6 +568,43 @@ const examples = [
|
||||
return <PressableAriaLabel />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Pressable with box-shadow',
|
||||
description: ('Pressables with box-shadow': string),
|
||||
render: function PressableWithBoxShadow(): React.Node {
|
||||
const [parentColor, setParentColor] = useState('red');
|
||||
const [childColor, setChildColor] = useState('blue');
|
||||
return (
|
||||
<Pressable
|
||||
pointerEvents="box-none"
|
||||
style={{
|
||||
width: 300,
|
||||
height: 300,
|
||||
overflow: 'hidden',
|
||||
transform: [{scale: 0.5}],
|
||||
boxShadow: '0 0 100px 0 rgba(0, 0, 0, 0.5)',
|
||||
backgroundColor: parentColor,
|
||||
}}
|
||||
onPress={() => {
|
||||
setParentColor(parentColor === 'red' ? 'orange' : 'red');
|
||||
}}>
|
||||
<Pressable
|
||||
style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
backgroundColor: childColor,
|
||||
height: 50,
|
||||
width: 100,
|
||||
}}
|
||||
onPress={() => {
|
||||
setChildColor(childColor === 'blue' ? 'green' : 'blue');
|
||||
}}
|
||||
/>
|
||||
</Pressable>
|
||||
);
|
||||
},
|
||||
},
|
||||
...PressableExampleFbInternal.examples,
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user