Hit testing now treats views with 0.0 opacity as interactable
Reviewed By: nicklockwood Differential Revision: D2574937 fb-gh-sync-id: 63629cd61cbd7cab93346b4dd91a32703d4f56e0
This commit is contained in:
parent
9f4da92195
commit
77272d5a91
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
Text,
|
||||
View,
|
||||
TouchableOpacity,
|
||||
} = React;
|
||||
|
||||
var TransparentHitTestExample = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<TouchableOpacity onPress={() => alert('Hi!')}>
|
||||
<Text>HELLO!</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<View style={{
|
||||
position: 'absolute',
|
||||
backgroundColor: 'green',
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
opacity: 0.0}} />
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
exports.title = '<TransparentHitTestExample>';
|
||||
exports.displayName = 'TransparentHitTestExample';
|
||||
exports.description = 'Transparent view receiving touch events';
|
||||
exports.examples = [
|
||||
{
|
||||
title: 'TransparentHitTestExample',
|
||||
render(): ReactElement { return <TransparentHitTestExample />; }
|
||||
}
|
||||
];
|
|
@ -50,6 +50,7 @@ var COMPONENTS = [
|
|||
require('./TextExample.ios'),
|
||||
require('./TextInputExample.ios'),
|
||||
require('./TouchableExample'),
|
||||
require('./TransparentHitTestExample'),
|
||||
require('./ViewExample'),
|
||||
require('./WebViewExample'),
|
||||
];
|
||||
|
|
|
@ -16,20 +16,6 @@
|
|||
#import "RCTUtils.h"
|
||||
#import "UIView+React.h"
|
||||
|
||||
static UIView *RCTViewHitTest(UIView *view, CGPoint point, UIEvent *event)
|
||||
{
|
||||
for (UIView *subview in [view.subviews reverseObjectEnumerator]) {
|
||||
if (!subview.isHidden && subview.isUserInteractionEnabled && subview.alpha > 0) {
|
||||
CGPoint convertedPoint = [subview convertPoint:point fromView:view];
|
||||
UIView *subviewHitTestView = [subview hitTest:convertedPoint withEvent:event];
|
||||
if (subviewHitTestView != nil) {
|
||||
return subviewHitTestView;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@implementation UIView (RCTViewUnmounting)
|
||||
|
||||
- (void)react_remountAllSubviews
|
||||
|
@ -150,18 +136,46 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused)
|
|||
|
||||
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
|
||||
{
|
||||
BOOL canReceiveTouchEvents = ([self isUserInteractionEnabled] && ![self isHidden]);
|
||||
if(!canReceiveTouchEvents) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// `hitSubview` is the topmost subview which was hit. The hit point can
|
||||
// be outside the bounds of `view` (e.g., if -clipsToBounds is NO).
|
||||
UIView *hitSubview = nil;
|
||||
BOOL isPointInside = [self pointInside:point withEvent:event];
|
||||
BOOL needsHitSubview = !(_pointerEvents == RCTPointerEventsNone || _pointerEvents == RCTPointerEventsBoxOnly);
|
||||
if (needsHitSubview && (![self clipsToBounds] || isPointInside)) {
|
||||
// The default behaviour of UIKit is that if a view does not contain a point,
|
||||
// then no subviews will be returned from hit testing, even if they contain
|
||||
// the hit point. By doing hit testing directly on the subviews, we bypass
|
||||
// the strict containment policy (i.e., UIKit guarantees that every ancestor
|
||||
// of the hit view will return YES from -pointInside:withEvent:). See:
|
||||
// - https://developer.apple.com/library/ios/qa/qa2013/qa1812.html
|
||||
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
|
||||
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
|
||||
hitSubview = [subview hitTest:convertedPoint withEvent:event];
|
||||
if (hitSubview != nil) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UIView *hitView = (isPointInside ? self : nil);
|
||||
|
||||
switch (_pointerEvents) {
|
||||
case RCTPointerEventsNone:
|
||||
return nil;
|
||||
case RCTPointerEventsUnspecified:
|
||||
return RCTViewHitTest(self, point, event) ?: [super hitTest:point withEvent:event];
|
||||
return hitSubview ?: hitView;
|
||||
case RCTPointerEventsBoxOnly:
|
||||
return [super hitTest:point withEvent:event] ? self: nil;
|
||||
return hitView;
|
||||
case RCTPointerEventsBoxNone:
|
||||
return RCTViewHitTest(self, point, event);
|
||||
return hitSubview;
|
||||
default:
|
||||
RCTLogError(@"Invalid pointer-events specified %zd on %@", _pointerEvents, self);
|
||||
return [super hitTest:point withEvent:event];
|
||||
return hitSubview ?: hitView;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue