Skip to content

Commit 258c6b1

Browse files
committed
Updates from Thu Feb 26
- [Children] Expose React.Children like web React | James Ide - Remove touch handler assertions - not always true | Tadeu Zagallo - [treehouse] Add support for clear button on UITextFields | Sumeet Vaidya - [Touch] Suite of touchable events on TouchableHighlight/Opacity | James Ide - [Images] Bail out when GIF data is in unexpected format instead of crashing | James Ide
1 parent 9bebc7e commit 258c6b1

File tree

9 files changed

+138
-18
lines changed

9 files changed

+138
-18
lines changed

Examples/UIExplorer/TouchableExample.js

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ var {
1212
StyleSheet,
1313
Text,
1414
TouchableHighlight,
15+
TouchableOpacity,
1516
View,
1617
} = React;
1718

@@ -57,6 +58,13 @@ exports.examples = [
5758
render: function() {
5859
return <TextOnPressBox />;
5960
},
61+
}, {
62+
title: 'Touchable feedback events',
63+
description: '<Touchable*> components accept onPress, onPressIn, ' +
64+
'onPressOut, and onLongPress as props.',
65+
render: function() {
66+
return <TouchableFeedbackEvents />;
67+
},
6068
}];
6169

6270
var TextOnPressBox = React.createClass({
@@ -95,11 +103,46 @@ var TextOnPressBox = React.createClass({
95103
}
96104
});
97105

106+
var TouchableFeedbackEvents = React.createClass({
107+
getInitialState: function() {
108+
return {
109+
eventLog: [],
110+
};
111+
},
112+
render: function() {
113+
return (
114+
<View>
115+
<View style={[styles.row, {justifyContent: 'center'}]}>
116+
<TouchableOpacity
117+
style={styles.wrapper}
118+
onPress={() => this._appendEvent('press')}
119+
onPressIn={() => this._appendEvent('pressIn')}
120+
onPressOut={() => this._appendEvent('pressOut')}
121+
onLongPress={() => this._appendEvent('longPress')}>
122+
<Text style={styles.button}>
123+
Press Me
124+
</Text>
125+
</TouchableOpacity>
126+
</View>
127+
<View style={styles.eventLogBox}>
128+
{this.state.eventLog.map((e, ii) => <Text key={ii}>{e}</Text>)}
129+
</View>
130+
</View>
131+
);
132+
},
133+
_appendEvent: function(eventName) {
134+
var limit = 6;
135+
var eventLog = this.state.eventLog.slice(0, limit - 1);
136+
eventLog.unshift(eventName);
137+
this.setState({eventLog});
138+
},
139+
});
140+
98141
var heartImage = {uri: 'https://pbs.twimg.com/media/BlXBfT3CQAA6cVZ.png:small'};
99142

100143
var styles = StyleSheet.create({
101144
row: {
102-
alignItems: 'center',
145+
justifyContent: 'center',
103146
flexDirection: 'row',
104147
},
105148
icon: {
@@ -113,6 +156,9 @@ var styles = StyleSheet.create({
113156
text: {
114157
fontSize: 16,
115158
},
159+
button: {
160+
color: '#007AFF',
161+
},
116162
wrapper: {
117163
borderRadius: 8,
118164
},
@@ -127,6 +173,14 @@ var styles = StyleSheet.create({
127173
borderColor: '#f0f0f0',
128174
backgroundColor: '#f9f9f9',
129175
},
176+
eventLogBox: {
177+
padding: 10,
178+
margin: 10,
179+
height: 120,
180+
borderWidth: 1 / PixelRatio.get(),
181+
borderColor: '#f0f0f0',
182+
backgroundColor: '#f9f9f9',
183+
},
130184
textBlock: {
131185
fontWeight: 'bold',
132186
color: 'blue',

Libraries/Components/TextInput/TextInput.ios.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,15 @@ var autoCapitalizeMode = {
6868
characters: nativeConstants.AllCharacters
6969
};
7070

71+
var clearButtonModeConstants = NativeModulesDeprecated.RKUIManager.UITextField.clearButtonMode;
72+
73+
var clearButtonModeTypes = {
74+
never: clearButtonModeConstants.Never,
75+
whileEditing: clearButtonModeConstants.WhileEditing,
76+
unlessEditing: clearButtonModeConstants.UnlessEditing,
77+
always: clearButtonModeConstants.Always,
78+
};
79+
7180
var keyboardType = {
7281
default: 'default',
7382
numeric: 'numeric',
@@ -90,6 +99,7 @@ var RKTextViewAttributes = merge(ReactIOSViewAttributes.UIView, {
9099
var RKTextFieldAttributes = merge(RKTextViewAttributes, {
91100
caretHidden: true,
92101
enabled: true,
102+
clearButtonMode: true,
93103
});
94104

95105
var onlyMultiline = {
@@ -105,6 +115,7 @@ var notMultiline = {
105115
var TextInput = React.createClass({
106116
statics: {
107117
autoCapitalizeMode: autoCapitalizeMode,
118+
clearButtonModeTypes: clearButtonModeTypes,
108119
keyboardType: keyboardType,
109120
},
110121

@@ -188,6 +199,10 @@ var TextInput = React.createClass({
188199
* and/or laggy typing, depending on how you process onChange events.
189200
*/
190201
controlled: PropTypes.bool,
202+
/**
203+
* When the clear button should appear on the right side of the text view
204+
*/
205+
clearButtonMode: PropTypes.oneOf(getObjectValues(clearButtonModeTypes)),
191206

192207
style: Text.stylePropType,
193208
},
@@ -316,6 +331,7 @@ var TextInput = React.createClass({
316331
text={this.state.bufferedValue}
317332
autoCapitalize={this.props.autoCapitalize}
318333
autoCorrect={this.props.autoCorrect}
334+
clearButtonMode={this.props.clearButtonMode}
319335
/>;
320336
} else {
321337
for (var propKey in notMultiline) {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Copyright 2004-present Facebook. All Rights Reserved.
3+
*
4+
* @providesModule TouchableFeedbackPropType
5+
* @flow
6+
*/
7+
'use strict';
8+
9+
var { PropTypes } = require('React');
10+
11+
var TouchableFeedbackPropType = {
12+
/**
13+
* Called when the touch is released, but not if cancelled (e.g. by a scroll
14+
* that steals the responder lock).
15+
*/
16+
onPress: PropTypes.func,
17+
onPressIn: PropTypes.func,
18+
onPressOut: PropTypes.func,
19+
onLongPress: PropTypes.func,
20+
};
21+
22+
module.exports = TouchableFeedbackPropType;

Libraries/Components/Touchable/TouchableHighlight.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
1111
var StyleSheet = require('StyleSheet');
1212
var TimerMixin = require('TimerMixin');
1313
var Touchable = require('Touchable');
14+
var TouchableFeedbackPropType = require('TouchableFeedbackPropType');
1415
var View = require('View');
1516

1617
var cloneWithProps = require('cloneWithProps');
@@ -50,6 +51,7 @@ var DEFAULT_PROPS = {
5051

5152
var TouchableHighlight = React.createClass({
5253
propTypes: {
54+
...TouchableFeedbackPropType,
5355
/**
5456
* Called when the touch is released, but not if cancelled (e.g. by
5557
* a scroll that steals the responder lock).
@@ -127,12 +129,14 @@ var TouchableHighlight = React.createClass({
127129
this.clearTimeout(this._hideTimeout);
128130
this._hideTimeout = null;
129131
this._showUnderlay();
132+
this.props.onPressIn && this.props.onPressIn();
130133
},
131134

132135
touchableHandleActivePressOut: function() {
133136
if (!this._hideTimeout) {
134137
this._hideUnderlay();
135138
}
139+
this.props.onPressOut && this.props.onPressOut();
136140
},
137141

138142
touchableHandlePress: function() {
@@ -142,6 +146,10 @@ var TouchableHighlight = React.createClass({
142146
this.props.onPress && this.props.onPress();
143147
},
144148

149+
touchableHandleLongPress: function() {
150+
this.props.onLongPress && this.props.onLongPress();
151+
},
152+
145153
touchableGetPressRectOffset: function() {
146154
return PRESS_RECT_OFFSET; // Always make sure to predeclare a constant!
147155
},

Libraries/Components/Touchable/TouchableOpacity.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ var NativeMethodsMixin = require('NativeMethodsMixin');
99
var POPAnimationMixin = require('POPAnimationMixin');
1010
var React = require('React');
1111
var Touchable = require('Touchable');
12+
var TouchableFeedbackPropType = require('TouchableFeedbackPropType');
1213

1314
var cloneWithProps = require('cloneWithProps');
1415
var ensureComponentIsNative = require('ensureComponentIsNative');
@@ -41,11 +42,7 @@ var TouchableOpacity = React.createClass({
4142
mixins: [Touchable.Mixin, NativeMethodsMixin, POPAnimationMixin],
4243

4344
propTypes: {
44-
/**
45-
* Called when the touch is released, but not if cancelled (e.g. by
46-
* a scroll that steals the responder lock).
47-
*/
48-
onPress: React.PropTypes.func,
45+
...TouchableFeedbackPropType,
4946
/**
5047
* Determines what the opacity of the wrapped view should be when touch is
5148
* active.
@@ -97,17 +94,23 @@ var TouchableOpacity = React.createClass({
9794
this.refs[CHILD_REF].setNativeProps({
9895
opacity: this.props.activeOpacity
9996
});
97+
this.props.onPressIn && this.props.onPressIn();
10098
},
10199

102100
touchableHandleActivePressOut: function() {
103101
this.setOpacityTo(1.0);
102+
this.props.onPressOut && this.props.onPressOut();
104103
},
105104

106105
touchableHandlePress: function() {
107106
this.setOpacityTo(1.0);
108107
this.props.onPress && this.props.onPress();
109108
},
110109

110+
touchableHandleLongPress: function() {
111+
this.props.onLongPress && this.props.onLongPress();
112+
},
113+
111114
touchableGetPressRectOffset: function() {
112115
return PRESS_RECT_OFFSET; // Always make sure to predeclare a constant!
113116
},

Libraries/Components/Touchable/TouchableWithoutFeedback.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
var React = require('React');
99
var Touchable = require('Touchable');
10-
var View = require('View');
10+
var TouchableFeedbackPropType = require('TouchableFeedbackPropType');
1111

1212
var copyProperties = require('copyProperties');
1313
var onlyChild = require('onlyChild');
@@ -29,12 +29,7 @@ var PRESS_RECT_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
2929
var TouchableWithoutFeedback = React.createClass({
3030
mixins: [Touchable.Mixin],
3131

32-
propTypes: {
33-
onPress: React.PropTypes.func,
34-
onPressIn: React.PropTypes.func,
35-
onPressOut: React.PropTypes.func,
36-
onLongPress: React.PropTypes.func,
37-
},
32+
propTypes: TouchableFeedbackPropType,
3833

3934
getInitialState: function() {
4035
return this.touchableGetInitialState();

Libraries/ReactIOS/ReactIOS.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
"use strict";
88

9+
var ReactChildren = require('ReactChildren');
910
var ReactComponent = require('ReactComponent');
1011
var ReactCompositeComponent = require('ReactCompositeComponent');
1112
var ReactContext = require('ReactContext');
@@ -20,6 +21,7 @@ var ReactPropTypes = require('ReactPropTypes');
2021

2122
var deprecated = require('deprecated');
2223
var invariant = require('invariant');
24+
var onlyChild = require('onlyChild');
2325

2426
ReactIOSDefaultInjection.inject();
2527

@@ -73,6 +75,12 @@ var render = function(component, mountInto) {
7375

7476
var ReactIOS = {
7577
hasReactIOSInitialized: false,
78+
Children: {
79+
map: ReactChildren.map,
80+
forEach: ReactChildren.forEach,
81+
count: ReactChildren.count,
82+
only: onlyChild
83+
},
7684
PropTypes: ReactPropTypes,
7785
createClass: ReactCompositeComponent.createClass,
7886
createElement: createElement,

ReactKit/Base/RCTConvert.m

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,9 @@ + (CAKeyframeAnimation *)GIF:(id)json
446446
}
447447

448448
imageSource = CGImageSourceCreateWithData((CFDataRef)data, NULL);
449+
} else {
450+
RCTLogMustFix(@"Expected NSString or NSData for GIF, received %@: %@", [json class], json);
451+
return nil;
449452
}
450453

451454
if (!UTTypeConformsTo(CGImageSourceGetType(imageSource), kUTTypeGIF)) {

ReactKit/Base/RCTTouchHandler.m

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,10 @@ - (void)_recordNewTouches:(NSSet *)touches
7777
}
7878
targetView = targetView.superview;
7979
}
80-
81-
RCTAssert(targetView.reactTag && targetView.userInteractionEnabled,
82-
@"No react view found for touch - something went wrong.");
80+
81+
if (!targetView.reactTag || !targetView.userInteractionEnabled) {
82+
return;
83+
}
8384

8485
// Get new, unique touch id
8586
const NSUInteger RCTMaxTouches = 11; // This is the maximum supported by iDevices
@@ -113,7 +114,10 @@ - (void)_recordRemovedTouches:(NSSet *)touches
113114
{
114115
for (UITouch *touch in touches) {
115116
NSUInteger index = [_nativeTouches indexOfObject:touch];
116-
RCTAssert(index != NSNotFound, @"Touch is already removed. This is a critical bug.");
117+
if(index == NSNotFound) {
118+
continue;
119+
}
120+
117121
[_touchViews removeObjectAtIndex:index];
118122
[_nativeTouches removeObjectAtIndex:index];
119123
[_reactTouches removeObjectAtIndex:index];
@@ -159,10 +163,17 @@ - (void)_updateAndDispatchTouches:(NSSet *)touches eventName:(NSString *)eventNa
159163
NSMutableArray *changedIndexes = [[NSMutableArray alloc] init];
160164
for (UITouch *touch in touches) {
161165
NSInteger index = [_nativeTouches indexOfObject:touch];
162-
RCTAssert(index != NSNotFound, @"Touch not found. This is a critical bug.");
166+
if (index == NSNotFound) {
167+
continue;
168+
}
169+
163170
[self _updateReactTouchAtIndex:index];
164171
[changedIndexes addObject:@(index)];
165172
}
173+
174+
if (changedIndexes.count == 0) {
175+
return;
176+
}
166177

167178
// Deep copy the touches because they will be accessed from another thread
168179
// TODO: would it be safer to do this in the bridge or executor, rather than trusting caller?

0 commit comments

Comments
 (0)