mirror of
https://github.com/status-im/react-native.git
synced 2025-01-27 01:40:08 +00:00
Improved 3D touch implementation, and added example
Summary: public This diff improves the implementation of 3D touch by adding a `forceTouchAvailable` constant to View that can be used to check if the feature is supported. I've also added an example of how you can use the `force` property of the touch event to measure touch pressure in React Native. Reviewed By: vjeux Differential Revision: D2864926 fb-gh-sync-id: 754c54989212ce4e4863716ceaba59673f0bb29d
This commit is contained in:
parent
21a4c6e853
commit
f685878938
@ -23,6 +23,7 @@ var {
|
||||
Text,
|
||||
TouchableHighlight,
|
||||
TouchableOpacity,
|
||||
UIManager,
|
||||
View,
|
||||
} = React;
|
||||
|
||||
@ -85,6 +86,13 @@ exports.examples = [
|
||||
render: function(): ReactElement {
|
||||
return <TouchableDelayEvents />;
|
||||
},
|
||||
}, {
|
||||
title: '3D Touch / Force Touch',
|
||||
description: 'iPhone 6s and 6s plus support 3D touch, which adds a force property to touches',
|
||||
render: function(): ReactElement {
|
||||
return <ForceTouchExample />;
|
||||
},
|
||||
platform: 'ios',
|
||||
}];
|
||||
|
||||
var TextOnPressBox = React.createClass({
|
||||
@ -133,18 +141,18 @@ var TouchableFeedbackEvents = React.createClass({
|
||||
return (
|
||||
<View testID="touchable_feedback_events">
|
||||
<View style={[styles.row, {justifyContent: 'center'}]}>
|
||||
<TouchableOpacity
|
||||
style={styles.wrapper}
|
||||
testID="touchable_feedback_events_button"
|
||||
onPress={() => this._appendEvent('press')}
|
||||
onPressIn={() => this._appendEvent('pressIn')}
|
||||
onPressOut={() => this._appendEvent('pressOut')}
|
||||
onLongPress={() => this._appendEvent('longPress')}>
|
||||
<Text style={styles.button}>
|
||||
Press Me
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={styles.wrapper}
|
||||
testID="touchable_feedback_events_button"
|
||||
onPress={() => this._appendEvent('press')}
|
||||
onPressIn={() => this._appendEvent('pressIn')}
|
||||
onPressOut={() => this._appendEvent('pressOut')}
|
||||
onLongPress={() => this._appendEvent('longPress')}>
|
||||
<Text style={styles.button}>
|
||||
Press Me
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View testID="touchable_feedback_events_console" style={styles.eventLogBox}>
|
||||
{this.state.eventLog.map((e, ii) => <Text key={ii}>{e}</Text>)}
|
||||
</View>
|
||||
@ -169,21 +177,21 @@ var TouchableDelayEvents = React.createClass({
|
||||
return (
|
||||
<View testID="touchable_delay_events">
|
||||
<View style={[styles.row, {justifyContent: 'center'}]}>
|
||||
<TouchableOpacity
|
||||
style={styles.wrapper}
|
||||
testID="touchable_delay_events_button"
|
||||
onPress={() => this._appendEvent('press')}
|
||||
delayPressIn={400}
|
||||
onPressIn={() => this._appendEvent('pressIn - 400ms delay')}
|
||||
delayPressOut={1000}
|
||||
onPressOut={() => this._appendEvent('pressOut - 1000ms delay')}
|
||||
delayLongPress={800}
|
||||
onLongPress={() => this._appendEvent('longPress - 800ms delay')}>
|
||||
<Text style={styles.button}>
|
||||
Press Me
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={styles.wrapper}
|
||||
testID="touchable_delay_events_button"
|
||||
onPress={() => this._appendEvent('press')}
|
||||
delayPressIn={400}
|
||||
onPressIn={() => this._appendEvent('pressIn - 400ms delay')}
|
||||
delayPressOut={1000}
|
||||
onPressOut={() => this._appendEvent('pressOut - 1000ms delay')}
|
||||
delayLongPress={800}
|
||||
onLongPress={() => this._appendEvent('longPress - 800ms delay')}>
|
||||
<Text style={styles.button}>
|
||||
Press Me
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.eventLogBox} testID="touchable_delay_events_console">
|
||||
{this.state.eventLog.map((e, ii) => <Text key={ii}>{e}</Text>)}
|
||||
</View>
|
||||
@ -198,6 +206,40 @@ var TouchableDelayEvents = React.createClass({
|
||||
},
|
||||
});
|
||||
|
||||
var ForceTouchExample = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
force: 0,
|
||||
};
|
||||
},
|
||||
_renderConsoleText: function() {
|
||||
return View.forceTouchAvailable ?
|
||||
'Force: ' + this.state.force.toFixed(3) :
|
||||
'3D Touch is not available on this device';
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<View testID="touchable_3dtouch_event">
|
||||
<View style={styles.forceTouchBox} testID="touchable_3dtouch_output">
|
||||
<Text>{this._renderConsoleText()}</Text>
|
||||
</View>
|
||||
<View style={[styles.row, {justifyContent: 'center'}]}>
|
||||
<View
|
||||
style={styles.wrapper}
|
||||
testID="touchable_3dtouch_button"
|
||||
onStartShouldSetResponder={() => true}
|
||||
onResponderMove={(event) => this.setState({force: event.nativeEvent.force})}
|
||||
onResponderRelease={(event) => this.setState({force: 0})}>
|
||||
<Text style={styles.button}>
|
||||
Press Me
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var heartImage = {uri: 'https://pbs.twimg.com/media/BlXBfT3CQAA6cVZ.png:small'};
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
@ -241,6 +283,14 @@ var styles = StyleSheet.create({
|
||||
borderColor: '#f0f0f0',
|
||||
backgroundColor: '#f9f9f9',
|
||||
},
|
||||
forceTouchBox: {
|
||||
padding: 10,
|
||||
margin: 10,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderColor: '#f0f0f0',
|
||||
backgroundColor: '#f9f9f9',
|
||||
alignItems: 'center',
|
||||
},
|
||||
textBlock: {
|
||||
fontWeight: '500',
|
||||
color: 'blue',
|
||||
|
@ -49,24 +49,24 @@ var queryLayoutByID = require('queryLayoutByID');
|
||||
*
|
||||
* - Choose the rendered component who's touches should start the interactive
|
||||
* sequence. On that rendered node, forward all `Touchable` responder
|
||||
* handlers. You can choose any rendered node you like. Choose a node who's
|
||||
* handlers. You can choose any rendered node you like. Choose a node whose
|
||||
* hit target you'd like to instigate the interaction sequence:
|
||||
*
|
||||
* // In render function:
|
||||
* return (
|
||||
* <div
|
||||
* <View
|
||||
* onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
|
||||
* onResponderTerminationRequest={this.touchableHandleResponderTerminationRequest}
|
||||
* onResponderGrant={this.touchableHandleResponderGrant}
|
||||
* onResponderMove={this.touchableHandleResponderMove}
|
||||
* onResponderRelease={this.touchableHandleResponderRelease}
|
||||
* onResponderTerminate={this.touchableHandleResponderTerminate}>
|
||||
* <div>
|
||||
* <View>
|
||||
* Even though the hit detection/interactions are triggered by the
|
||||
* wrapping (typically larger) node, we usually end up implementing
|
||||
* custom logic that highlights this inner one.
|
||||
* </div>
|
||||
* </div>
|
||||
* </View>
|
||||
* </View>
|
||||
* );
|
||||
*
|
||||
* - You may set up your own handlers for each of these events, so long as you
|
||||
|
@ -11,20 +11,20 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var PropTypes = require('ReactPropTypes');
|
||||
var React = require('React');
|
||||
var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
|
||||
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||
var StyleSheetPropType = require('StyleSheetPropType');
|
||||
var UIManager = require('UIManager');
|
||||
var ViewStylePropTypes = require('ViewStylePropTypes');
|
||||
const NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
const PropTypes = require('ReactPropTypes');
|
||||
const React = require('React');
|
||||
const ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
|
||||
const ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||
const StyleSheetPropType = require('StyleSheetPropType');
|
||||
const UIManager = require('UIManager');
|
||||
const ViewStylePropTypes = require('ViewStylePropTypes');
|
||||
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
const requireNativeComponent = require('requireNativeComponent');
|
||||
|
||||
var stylePropType = StyleSheetPropType(ViewStylePropTypes);
|
||||
const stylePropType = StyleSheetPropType(ViewStylePropTypes);
|
||||
|
||||
var AccessibilityTraits = [
|
||||
const AccessibilityTraits = [
|
||||
'none',
|
||||
'button',
|
||||
'link',
|
||||
@ -44,13 +44,26 @@ var AccessibilityTraits = [
|
||||
'pageTurn',
|
||||
];
|
||||
|
||||
var AccessibilityComponentType = [
|
||||
const AccessibilityComponentType = [
|
||||
'none',
|
||||
'button',
|
||||
'radiobutton_checked',
|
||||
'radiobutton_unchecked',
|
||||
];
|
||||
|
||||
const forceTouchAvailable = (UIManager.RCTView.Constants &&
|
||||
UIManager.RCTView.Constants.forceTouchAvailable) || false;
|
||||
|
||||
const statics = {
|
||||
AccessibilityTraits,
|
||||
AccessibilityComponentType,
|
||||
/**
|
||||
* Is 3D Touch / Force Touch available (i.e. will touch events include `force`)
|
||||
* @platform ios
|
||||
*/
|
||||
forceTouchAvailable,
|
||||
};
|
||||
|
||||
/**
|
||||
* The most fundamental component for building UI, `View` is a
|
||||
* container that supports layout with flexbox, style, some touch handling, and
|
||||
@ -71,7 +84,7 @@ var AccessibilityComponentType = [
|
||||
* `View`s are designed to be used with `StyleSheet`s for clarity and
|
||||
* performance, although inline styles are also supported.
|
||||
*/
|
||||
var View = React.createClass({
|
||||
const View = React.createClass({
|
||||
mixins: [NativeMethodsMixin],
|
||||
|
||||
/**
|
||||
@ -84,8 +97,7 @@ var View = React.createClass({
|
||||
},
|
||||
|
||||
statics: {
|
||||
AccessibilityTraits,
|
||||
AccessibilityComponentType,
|
||||
...statics,
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
@ -320,16 +332,16 @@ var View = React.createClass({
|
||||
},
|
||||
});
|
||||
|
||||
var RCTView = requireNativeComponent('RCTView', View, {
|
||||
const RCTView = requireNativeComponent('RCTView', View, {
|
||||
nativeOnly: {
|
||||
nativeBackgroundAndroid: true,
|
||||
}
|
||||
});
|
||||
|
||||
if (__DEV__) {
|
||||
var viewConfig = UIManager.viewConfigs && UIManager.viewConfigs.RCTView || {};
|
||||
for (var prop in viewConfig.nativeProps) {
|
||||
var viewAny: any = View; // Appease flow
|
||||
const viewConfig = UIManager.viewConfigs && UIManager.viewConfigs.RCTView || {};
|
||||
for (const prop in viewConfig.nativeProps) {
|
||||
const viewAny: any = View; // Appease flow
|
||||
if (!viewAny.propTypes[prop] && !ReactNativeStyleAttributes[prop]) {
|
||||
throw new Error(
|
||||
'View is missing propType for native prop `' + prop + '`'
|
||||
@ -338,9 +350,11 @@ if (__DEV__) {
|
||||
}
|
||||
}
|
||||
|
||||
var ViewToExport = RCTView;
|
||||
let ViewToExport = RCTView;
|
||||
if (__DEV__) {
|
||||
ViewToExport = View;
|
||||
} else {
|
||||
Object.assign(RCTView, statics);
|
||||
}
|
||||
|
||||
module.exports = ViewToExport;
|
||||
|
@ -108,11 +108,9 @@ typedef NS_ENUM(NSInteger, RCTTouchEventType) {
|
||||
}
|
||||
|
||||
// Create touch
|
||||
NSMutableDictionary *reactTouch = [[NSMutableDictionary alloc] initWithCapacity:11];
|
||||
NSMutableDictionary *reactTouch = [[NSMutableDictionary alloc] initWithCapacity:RCTMaxTouches];
|
||||
reactTouch[@"target"] = reactTag;
|
||||
reactTouch[@"identifier"] = @(touchID);
|
||||
reactTouch[@"touches"] = (id)kCFNull; // We hijack this touchObj to serve both as an event
|
||||
reactTouch[@"changedTouches"] = (id)kCFNull; // and as a Touch object, so making this JIT friendly.
|
||||
|
||||
// Add to arrays
|
||||
[_touchViews addObject:targetView];
|
||||
@ -151,10 +149,11 @@ typedef NS_ENUM(NSInteger, RCTTouchEventType) {
|
||||
reactTouch[@"locationY"] = @(touchViewLocation.y);
|
||||
reactTouch[@"timestamp"] = @(nativeTouch.timestamp * 1000); // in ms, for JS
|
||||
|
||||
if ([nativeTouch.view respondsToSelector:@selector(traitCollection)] &&
|
||||
[nativeTouch.view.traitCollection respondsToSelector:@selector(forceTouchCapability)]) {
|
||||
|
||||
reactTouch[@"force"] = @(RCTZeroIfNaN(nativeTouch.force/nativeTouch.maximumPossibleForce));
|
||||
// TODO: force for a 'normal' touch is usually 1.0;
|
||||
// should we expose a `normalTouchForce` constant somewhere (which would
|
||||
// have a value of `1.0 / nativeTouch.maximumPossibleForce`)?
|
||||
if (RCTForceTouchAvailable()) {
|
||||
reactTouch[@"force"] = @(RCTZeroIfNaN(nativeTouch.force / nativeTouch.maximumPossibleForce));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,6 +75,9 @@ RCT_EXTERN UIApplication *__nullable RCTSharedApplication(void);
|
||||
// or view controller, e.g. to present a modal view controller or alert.
|
||||
RCT_EXTERN UIWindow *__nullable RCTKeyWindow(void);
|
||||
|
||||
// Does this device support force touch (aka 3D Touch)?
|
||||
RCT_EXTERN BOOL RCTForceTouchAvailable(void);
|
||||
|
||||
// Return a UIAlertView initialized with the given values
|
||||
// or nil if running in an app extension
|
||||
RCT_EXTERN UIAlertView *__nullable RCTAlertView(NSString *title,
|
||||
|
@ -434,6 +434,19 @@ UIWindow *__nullable RCTKeyWindow(void)
|
||||
return RCTSharedApplication().keyWindow;
|
||||
}
|
||||
|
||||
BOOL RCTForceTouchAvailable(void)
|
||||
{
|
||||
static BOOL forceSupported;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
forceSupported = [UITraitCollection class] &&
|
||||
[UITraitCollection instancesRespondToSelector:@selector(forceTouchCapability)];
|
||||
});
|
||||
|
||||
return forceSupported &&
|
||||
(RCTKeyWindow() ?: [UIView new]).traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable;
|
||||
}
|
||||
|
||||
UIAlertView *__nullable RCTAlertView(NSString *title,
|
||||
NSString *__nullable message,
|
||||
id __nullable delegate,
|
||||
@ -475,7 +488,7 @@ id __nullable RCTNilIfNull(id __nullable value)
|
||||
return value == (id)kCFNull ? nil : value;
|
||||
}
|
||||
|
||||
RCT_EXTERN double RCTZeroIfNaN(double value)
|
||||
double RCTZeroIfNaN(double value)
|
||||
{
|
||||
return isnan(value) || isinf(value) ? 0 : value;
|
||||
}
|
||||
|
@ -91,6 +91,11 @@ RCT_EXPORT_MODULE()
|
||||
return @[];
|
||||
}
|
||||
|
||||
- (NSDictionary<NSString *, id> *)constantsToExport
|
||||
{
|
||||
return @{@"forceTouchAvailable": @(RCTForceTouchAvailable())};
|
||||
}
|
||||
|
||||
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(__unused RCTShadowView *)shadowView
|
||||
{
|
||||
return nil;
|
||||
|
Loading…
x
Reference in New Issue
Block a user