Skip to content

Commit

Permalink
Updates from Thu Feb 26
Browse files Browse the repository at this point in the history
- [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
  • Loading branch information
vjeux committed Mar 2, 2015
1 parent 9bebc7e commit 258c6b1
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 18 deletions.
56 changes: 55 additions & 1 deletion Examples/UIExplorer/TouchableExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ var {
StyleSheet,
Text,
TouchableHighlight,
TouchableOpacity,
View,
} = React;

Expand Down Expand Up @@ -57,6 +58,13 @@ exports.examples = [
render: function() {
return <TextOnPressBox />;
},
}, {
title: 'Touchable feedback events',
description: '<Touchable*> components accept onPress, onPressIn, ' +
'onPressOut, and onLongPress as props.',
render: function() {
return <TouchableFeedbackEvents />;
},
}];

var TextOnPressBox = React.createClass({
Expand Down Expand Up @@ -95,11 +103,46 @@ var TextOnPressBox = React.createClass({
}
});

var TouchableFeedbackEvents = React.createClass({
getInitialState: function() {
return {
eventLog: [],
};
},
render: function() {
return (
<View>
<View style={[styles.row, {justifyContent: 'center'}]}>
<TouchableOpacity
style={styles.wrapper}
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 style={styles.eventLogBox}>
{this.state.eventLog.map((e, ii) => <Text key={ii}>{e}</Text>)}
</View>
</View>
);
},
_appendEvent: function(eventName) {
var limit = 6;
var eventLog = this.state.eventLog.slice(0, limit - 1);
eventLog.unshift(eventName);
this.setState({eventLog});
},
});

var heartImage = {uri: 'https://pbs.twimg.com/media/BlXBfT3CQAA6cVZ.png:small'};

var styles = StyleSheet.create({
row: {
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
},
icon: {
Expand All @@ -113,6 +156,9 @@ var styles = StyleSheet.create({
text: {
fontSize: 16,
},
button: {
color: '#007AFF',
},
wrapper: {
borderRadius: 8,
},
Expand All @@ -127,6 +173,14 @@ var styles = StyleSheet.create({
borderColor: '#f0f0f0',
backgroundColor: '#f9f9f9',
},
eventLogBox: {
padding: 10,
margin: 10,
height: 120,
borderWidth: 1 / PixelRatio.get(),
borderColor: '#f0f0f0',
backgroundColor: '#f9f9f9',
},
textBlock: {
fontWeight: 'bold',
color: 'blue',
Expand Down
16 changes: 16 additions & 0 deletions Libraries/Components/TextInput/TextInput.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ var autoCapitalizeMode = {
characters: nativeConstants.AllCharacters
};

var clearButtonModeConstants = NativeModulesDeprecated.RKUIManager.UITextField.clearButtonMode;

var clearButtonModeTypes = {
never: clearButtonModeConstants.Never,
whileEditing: clearButtonModeConstants.WhileEditing,
unlessEditing: clearButtonModeConstants.UnlessEditing,
always: clearButtonModeConstants.Always,
};

var keyboardType = {
default: 'default',
numeric: 'numeric',
Expand All @@ -90,6 +99,7 @@ var RKTextViewAttributes = merge(ReactIOSViewAttributes.UIView, {
var RKTextFieldAttributes = merge(RKTextViewAttributes, {
caretHidden: true,
enabled: true,
clearButtonMode: true,
});

var onlyMultiline = {
Expand All @@ -105,6 +115,7 @@ var notMultiline = {
var TextInput = React.createClass({
statics: {
autoCapitalizeMode: autoCapitalizeMode,
clearButtonModeTypes: clearButtonModeTypes,
keyboardType: keyboardType,
},

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

style: Text.stylePropType,
},
Expand Down Expand Up @@ -316,6 +331,7 @@ var TextInput = React.createClass({
text={this.state.bufferedValue}
autoCapitalize={this.props.autoCapitalize}
autoCorrect={this.props.autoCorrect}
clearButtonMode={this.props.clearButtonMode}
/>;
} else {
for (var propKey in notMultiline) {
Expand Down
22 changes: 22 additions & 0 deletions Libraries/Components/Touchable/TouchableFeedbackPropType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* @providesModule TouchableFeedbackPropType
* @flow
*/
'use strict';

var { PropTypes } = require('React');

var TouchableFeedbackPropType = {
/**
* Called when the touch is released, but not if cancelled (e.g. by a scroll
* that steals the responder lock).
*/
onPress: PropTypes.func,
onPressIn: PropTypes.func,
onPressOut: PropTypes.func,
onLongPress: PropTypes.func,
};

module.exports = TouchableFeedbackPropType;
8 changes: 8 additions & 0 deletions Libraries/Components/Touchable/TouchableHighlight.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
var StyleSheet = require('StyleSheet');
var TimerMixin = require('TimerMixin');
var Touchable = require('Touchable');
var TouchableFeedbackPropType = require('TouchableFeedbackPropType');
var View = require('View');

var cloneWithProps = require('cloneWithProps');
Expand Down Expand Up @@ -50,6 +51,7 @@ var DEFAULT_PROPS = {

var TouchableHighlight = React.createClass({
propTypes: {
...TouchableFeedbackPropType,
/**
* Called when the touch is released, but not if cancelled (e.g. by
* a scroll that steals the responder lock).
Expand Down Expand Up @@ -127,12 +129,14 @@ var TouchableHighlight = React.createClass({
this.clearTimeout(this._hideTimeout);
this._hideTimeout = null;
this._showUnderlay();
this.props.onPressIn && this.props.onPressIn();
},

touchableHandleActivePressOut: function() {
if (!this._hideTimeout) {
this._hideUnderlay();
}
this.props.onPressOut && this.props.onPressOut();
},

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

touchableHandleLongPress: function() {
this.props.onLongPress && this.props.onLongPress();
},

touchableGetPressRectOffset: function() {
return PRESS_RECT_OFFSET; // Always make sure to predeclare a constant!
},
Expand Down
13 changes: 8 additions & 5 deletions Libraries/Components/Touchable/TouchableOpacity.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var NativeMethodsMixin = require('NativeMethodsMixin');
var POPAnimationMixin = require('POPAnimationMixin');
var React = require('React');
var Touchable = require('Touchable');
var TouchableFeedbackPropType = require('TouchableFeedbackPropType');

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

propTypes: {
/**
* Called when the touch is released, but not if cancelled (e.g. by
* a scroll that steals the responder lock).
*/
onPress: React.PropTypes.func,
...TouchableFeedbackPropType,
/**
* Determines what the opacity of the wrapped view should be when touch is
* active.
Expand Down Expand Up @@ -97,17 +94,23 @@ var TouchableOpacity = React.createClass({
this.refs[CHILD_REF].setNativeProps({
opacity: this.props.activeOpacity
});
this.props.onPressIn && this.props.onPressIn();
},

touchableHandleActivePressOut: function() {
this.setOpacityTo(1.0);
this.props.onPressOut && this.props.onPressOut();
},

touchableHandlePress: function() {
this.setOpacityTo(1.0);
this.props.onPress && this.props.onPress();
},

touchableHandleLongPress: function() {
this.props.onLongPress && this.props.onLongPress();
},

touchableGetPressRectOffset: function() {
return PRESS_RECT_OFFSET; // Always make sure to predeclare a constant!
},
Expand Down
9 changes: 2 additions & 7 deletions Libraries/Components/Touchable/TouchableWithoutFeedback.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

var React = require('React');
var Touchable = require('Touchable');
var View = require('View');
var TouchableFeedbackPropType = require('TouchableFeedbackPropType');

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

propTypes: {
onPress: React.PropTypes.func,
onPressIn: React.PropTypes.func,
onPressOut: React.PropTypes.func,
onLongPress: React.PropTypes.func,
},
propTypes: TouchableFeedbackPropType,

getInitialState: function() {
return this.touchableGetInitialState();
Expand Down
8 changes: 8 additions & 0 deletions Libraries/ReactIOS/ReactIOS.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

"use strict";

var ReactChildren = require('ReactChildren');
var ReactComponent = require('ReactComponent');
var ReactCompositeComponent = require('ReactCompositeComponent');
var ReactContext = require('ReactContext');
Expand All @@ -20,6 +21,7 @@ var ReactPropTypes = require('ReactPropTypes');

var deprecated = require('deprecated');
var invariant = require('invariant');
var onlyChild = require('onlyChild');

ReactIOSDefaultInjection.inject();

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

var ReactIOS = {
hasReactIOSInitialized: false,
Children: {
map: ReactChildren.map,
forEach: ReactChildren.forEach,
count: ReactChildren.count,
only: onlyChild
},
PropTypes: ReactPropTypes,
createClass: ReactCompositeComponent.createClass,
createElement: createElement,
Expand Down
3 changes: 3 additions & 0 deletions ReactKit/Base/RCTConvert.m
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,9 @@ + (CAKeyframeAnimation *)GIF:(id)json
}

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

if (!UTTypeConformsTo(CGImageSourceGetType(imageSource), kUTTypeGIF)) {
Expand Down
21 changes: 16 additions & 5 deletions ReactKit/Base/RCTTouchHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ - (void)_recordNewTouches:(NSSet *)touches
}
targetView = targetView.superview;
}

RCTAssert(targetView.reactTag && targetView.userInteractionEnabled,
@"No react view found for touch - something went wrong.");

if (!targetView.reactTag || !targetView.userInteractionEnabled) {
return;
}

// Get new, unique touch id
const NSUInteger RCTMaxTouches = 11; // This is the maximum supported by iDevices
Expand Down Expand Up @@ -113,7 +114,10 @@ - (void)_recordRemovedTouches:(NSSet *)touches
{
for (UITouch *touch in touches) {
NSUInteger index = [_nativeTouches indexOfObject:touch];
RCTAssert(index != NSNotFound, @"Touch is already removed. This is a critical bug.");
if(index == NSNotFound) {
continue;
}

[_touchViews removeObjectAtIndex:index];
[_nativeTouches removeObjectAtIndex:index];
[_reactTouches removeObjectAtIndex:index];
Expand Down Expand Up @@ -159,10 +163,17 @@ - (void)_updateAndDispatchTouches:(NSSet *)touches eventName:(NSString *)eventNa
NSMutableArray *changedIndexes = [[NSMutableArray alloc] init];
for (UITouch *touch in touches) {
NSInteger index = [_nativeTouches indexOfObject:touch];
RCTAssert(index != NSNotFound, @"Touch not found. This is a critical bug.");
if (index == NSNotFound) {
continue;
}

[self _updateReactTouchAtIndex:index];
[changedIndexes addObject:@(index)];
}

if (changedIndexes.count == 0) {
return;
}

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

0 comments on commit 258c6b1

Please sign in to comment.