diff --git a/Examples/UIExplorer/TouchableExample.js b/Examples/UIExplorer/TouchableExample.js
index 7b0bf2c0b..dd3b2c9c8 100644
--- a/Examples/UIExplorer/TouchableExample.js
+++ b/Examples/UIExplorer/TouchableExample.js
@@ -23,6 +23,7 @@ var {
Text,
TouchableHighlight,
TouchableOpacity,
+ UIManager,
View,
} = React;
@@ -85,6 +86,13 @@ exports.examples = [
render: function(): ReactElement {
return ;
},
+}, {
+ 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 ;
+ },
+ platform: 'ios',
}];
var TextOnPressBox = React.createClass({
@@ -133,18 +141,18 @@ var TouchableFeedbackEvents = React.createClass({
return (
- this._appendEvent('press')}
- onPressIn={() => this._appendEvent('pressIn')}
- onPressOut={() => this._appendEvent('pressOut')}
- onLongPress={() => this._appendEvent('longPress')}>
-
- Press Me
-
-
-
+ this._appendEvent('press')}
+ onPressIn={() => this._appendEvent('pressIn')}
+ onPressOut={() => this._appendEvent('pressOut')}
+ onLongPress={() => this._appendEvent('longPress')}>
+
+ Press Me
+
+
+
{this.state.eventLog.map((e, ii) => {e})}
@@ -169,21 +177,21 @@ var TouchableDelayEvents = React.createClass({
return (
- 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')}>
-
- Press Me
-
-
-
+ 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')}>
+
+ Press Me
+
+
+
{this.state.eventLog.map((e, ii) => {e})}
@@ -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 (
+
+
+ {this._renderConsoleText()}
+
+
+ true}
+ onResponderMove={(event) => this.setState({force: event.nativeEvent.force})}
+ onResponderRelease={(event) => this.setState({force: 0})}>
+
+ Press Me
+
+
+
+
+ );
+ },
+});
+
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',
diff --git a/Libraries/Components/Touchable/Touchable.js b/Libraries/Components/Touchable/Touchable.js
index e0ea8850b..9f457c5c0 100644
--- a/Libraries/Components/Touchable/Touchable.js
+++ b/Libraries/Components/Touchable/Touchable.js
@@ -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 (
- *
- *
+ *
* 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.
- *
- *
+ *
+ *
* );
*
* - You may set up your own handlers for each of these events, so long as you
diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js
index beaffb75c..a7f31f9a0 100644
--- a/Libraries/Components/View/View.js
+++ b/Libraries/Components/View/View.js
@@ -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;
diff --git a/React/Base/RCTTouchHandler.m b/React/Base/RCTTouchHandler.m
index c0f6b6f42..f3050cfe9 100644
--- a/React/Base/RCTTouchHandler.m
+++ b/React/Base/RCTTouchHandler.m
@@ -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));
}
}
diff --git a/React/Base/RCTUtils.h b/React/Base/RCTUtils.h
index 8f7a4858b..c751dabbd 100644
--- a/React/Base/RCTUtils.h
+++ b/React/Base/RCTUtils.h
@@ -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,
diff --git a/React/Base/RCTUtils.m b/React/Base/RCTUtils.m
index f49e117e2..68e3a15e5 100644
--- a/React/Base/RCTUtils.m
+++ b/React/Base/RCTUtils.m
@@ -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;
}
diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m
index bbbca7da1..68980e0bf 100644
--- a/React/Views/RCTViewManager.m
+++ b/React/Views/RCTViewManager.m
@@ -91,6 +91,11 @@ RCT_EXPORT_MODULE()
return @[];
}
+- (NSDictionary *)constantsToExport
+{
+ return @{@"forceTouchAvailable": @(RCTForceTouchAvailable())};
+}
+
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(__unused RCTShadowView *)shadowView
{
return nil;