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:
Milen Dzhumerov 2015-10-30 04:11:04 -07:00 committed by facebook-github-bot-4
parent 9f4da92195
commit 77272d5a91
3 changed files with 83 additions and 18 deletions

View File

@ -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 />; }
}
];

View File

@ -50,6 +50,7 @@ var COMPONENTS = [
require('./TextExample.ios'),
require('./TextInputExample.ios'),
require('./TouchableExample'),
require('./TransparentHitTestExample'),
require('./ViewExample'),
require('./WebViewExample'),
];

View File

@ -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;
}
}