diff --git a/Libraries/Components/AppleTV/TVViewPropTypes.js b/Libraries/Components/AppleTV/TVViewPropTypes.js index cf5f5b03b..fab7a30b8 100644 --- a/Libraries/Components/AppleTV/TVViewPropTypes.js +++ b/Libraries/Components/AppleTV/TVViewPropTypes.js @@ -37,6 +37,9 @@ var TVViewPropTypes = { * shiftDistanceY: Defaults to 2.0. * tiltAngle: Defaults to 0.05. * magnification: Defaults to 1.0. + * pressMagnification: Defaults to 1.0. + * pressDuration: Defaults to 0.3. + * pressDelay: Defaults to 0.0. * * @platform ios */ diff --git a/Libraries/Components/Touchable/TouchableHighlight.js b/Libraries/Components/Touchable/TouchableHighlight.js index 6493a5147..19680b236 100644 --- a/Libraries/Components/Touchable/TouchableHighlight.js +++ b/Libraries/Components/Touchable/TouchableHighlight.js @@ -13,6 +13,7 @@ const ColorPropType = require('ColorPropType'); const NativeMethodsMixin = require('NativeMethodsMixin'); const PropTypes = require('prop-types'); +const Platform = require('Platform'); const React = require('React'); const ReactNativeViewAttributes = require('ReactNativeViewAttributes'); const StyleSheet = require('StyleSheet'); @@ -172,6 +173,9 @@ const TouchableHighlight = createReactClass({ * shiftDistanceY: Defaults to 2.0. * tiltAngle: Defaults to 0.05. * magnification: Defaults to 1.0. + * pressMagnification: Defaults to 1.0. + * pressDuration: Defaults to 0.3. + * pressDelay: Defaults to 0.0. * * @platform ios */ @@ -246,11 +250,13 @@ const TouchableHighlight = createReactClass({ touchableHandlePress: function(e: PressEvent) { clearTimeout(this._hideTimeout); - this._showUnderlay(); - this._hideTimeout = setTimeout( - this._hideUnderlay, - this.props.delayPressOut, - ); + if (!Platform.isTVOS) { + this._showUnderlay(); + this._hideTimeout = setTimeout( + this._hideUnderlay, + this.props.delayPressOut, + ); + } this.props.onPress && this.props.onPress(e); }, diff --git a/RNTester/js/ListExampleShared.js b/RNTester/js/ListExampleShared.js index 96ace6156..c5eef14db 100644 --- a/RNTester/js/ListExampleShared.js +++ b/RNTester/js/ListExampleShared.js @@ -62,6 +62,9 @@ class ItemComponent extends React.PureComponent<{ onPress={this._onPress} onShowUnderlay={this.props.onShowUnderlay} onHideUnderlay={this.props.onHideUnderlay} + tvParallaxProperties={{ + pressMagnification: 1.1, + }} style={horizontal ? styles.horizItem : styles.item}> diff --git a/RNTester/js/RNTesterList.ios.js b/RNTester/js/RNTesterList.ios.js index 1542a4e4a..d45e42fa5 100644 --- a/RNTester/js/RNTesterList.ios.js +++ b/RNTester/js/RNTesterList.ios.js @@ -189,7 +189,7 @@ const ComponentExamples: Array = [ { key: 'TouchableExample', module: require('./TouchableExample'), - supportsTVOS: false, + supportsTVOS: true, }, { key: 'TransparentHitTestExample', diff --git a/RNTester/js/TouchableExample.js b/RNTester/js/TouchableExample.js index f50589117..e0c227d9c 100644 --- a/RNTester/js/TouchableExample.js +++ b/RNTester/js/TouchableExample.js @@ -55,6 +55,7 @@ exports.examples = [ style={styles.wrapper} activeOpacity={1} animationVelocity={0} + tvParallaxProperties={{pressMagnification: 1.3, pressDuration: 0.6}} underlayColor="rgb(210, 230, 255)" onPress={() => console.log('custom THW text - highlight')}> diff --git a/React/Views/RCTTVView.m b/React/Views/RCTTVView.m index dcd086499..435197862 100644 --- a/React/Views/RCTTVView.m +++ b/React/Views/RCTTVView.m @@ -27,24 +27,50 @@ - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { - self.tvParallaxProperties = @{ - @"enabled": @YES, - @"shiftDistanceX": @2.0f, - @"shiftDistanceY": @2.0f, - @"tiltAngle": @0.05f, - @"magnification": @1.0f - }; + dispatch_once(&onceToken, ^{ + defaultTVParallaxProperties = @{ + @"enabled": @YES, + @"shiftDistanceX": @2.0f, + @"shiftDistanceY": @2.0f, + @"tiltAngle": @0.05f, + @"magnification": @1.0f, + @"pressMagnification": @1.0f, + @"pressDuration": @0.3f, + @"pressDelay": @0.0f + }; + }); + self.tvParallaxProperties = defaultTVParallaxProperties; } return self; } +static NSDictionary* defaultTVParallaxProperties = nil; +static dispatch_once_t onceToken; + +- (void)setTvParallaxProperties:(NSDictionary *)tvParallaxProperties { + if (_tvParallaxProperties == nil) { + _tvParallaxProperties = [defaultTVParallaxProperties copy]; + return; + } + + NSMutableDictionary *newParallaxProperties = [NSMutableDictionary dictionaryWithDictionary:_tvParallaxProperties]; + for (NSString *k in [defaultTVParallaxProperties allKeys]) { + if (tvParallaxProperties[k]) { + newParallaxProperties[k] = tvParallaxProperties[k]; + } + } + _tvParallaxProperties = [newParallaxProperties copy]; +} + RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused) - (void)setIsTVSelectable:(BOOL)isTVSelectable { self->_isTVSelectable = isTVSelectable; - if(isTVSelectable) { - UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSelect:)]; + if (isTVSelectable) { + UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] + initWithTarget:self + action:@selector(handleSelect:)]; recognizer.allowedPressTypes = @[@(UIPressTypeSelect)]; _selectRecognizer = recognizer; [self addGestureRecognizer:_selectRecognizer]; @@ -57,8 +83,37 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused) - (void)handleSelect:(__unused UIGestureRecognizer *)r { - [[NSNotificationCenter defaultCenter] postNotificationName:RCTTVNavigationEventNotification - object:@{@"eventType":@"select",@"tag":self.reactTag}]; + if ([self.tvParallaxProperties[@"enabled"] boolValue] == YES) { + float magnification = [self.tvParallaxProperties[@"magnification"] floatValue]; + float pressMagnification = [self.tvParallaxProperties[@"pressMagnification"] floatValue]; + + // Duration of press animation + float pressDuration = [self.tvParallaxProperties[@"pressDuration"] floatValue]; + + // Delay of press animation + float pressDelay = [self.tvParallaxProperties[@"pressDelay"] floatValue]; + + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:pressDelay]]; + + [UIView animateWithDuration:(pressDuration/2) + animations:^{ + self.transform = CGAffineTransformMakeScale(pressMagnification, pressMagnification); + } + completion:^(__unused BOOL finished1){ + [UIView animateWithDuration:(pressDuration/2) + animations:^{ + self.transform = CGAffineTransformMakeScale(magnification, magnification); + } + completion:^(__unused BOOL finished2) { + [[NSNotificationCenter defaultCenter] postNotificationName:RCTTVNavigationEventNotification + object:@{@"eventType":@"select",@"tag":self.reactTag}]; + }]; + }]; + + } else { + [[NSNotificationCenter defaultCenter] postNotificationName:RCTTVNavigationEventNotification + object:@{@"eventType":@"select",@"tag":self.reactTag}]; + } } - (BOOL)isUserInteractionEnabled @@ -79,15 +134,15 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused) // Make horizontal movements shift the centre left and right UIInterpolatingMotionEffect *xShift = [[UIInterpolatingMotionEffect alloc] - initWithKeyPath:@"center.x" - type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; + initWithKeyPath:@"center.x" + type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; xShift.minimumRelativeValue = @( shiftDistanceX * -1.0f); xShift.maximumRelativeValue = @( shiftDistanceX); // Make vertical movements shift the centre up and down UIInterpolatingMotionEffect *yShift = [[UIInterpolatingMotionEffect alloc] - initWithKeyPath:@"center.y" - type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis]; + initWithKeyPath:@"center.y" + type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis]; yShift.minimumRelativeValue = @( shiftDistanceY * -1.0f); yShift.maximumRelativeValue = @( shiftDistanceY); @@ -95,7 +150,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused) CGFloat const tiltAngle = [self.tvParallaxProperties[@"tiltAngle"] floatValue]; // Now make horizontal movements effect a rotation about the Y axis for side-to-side rotation. - UIInterpolatingMotionEffect *xTilt = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"layer.transform" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; + UIInterpolatingMotionEffect *xTilt = [[UIInterpolatingMotionEffect alloc] + initWithKeyPath:@"layer.transform" + type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis]; // CATransform3D value for minimumRelativeValue CATransform3D transMinimumTiltAboutY = CATransform3DIdentity; @@ -112,7 +169,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused) xTilt.maximumRelativeValue = [NSValue valueWithCATransform3D: transMaximumTiltAboutY]; // Now make vertical movements effect a rotation about the X axis for up and down rotation. - UIInterpolatingMotionEffect *yTilt = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"layer.transform" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis]; + UIInterpolatingMotionEffect *yTilt = [[UIInterpolatingMotionEffect alloc] + initWithKeyPath:@"layer.transform" + type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis]; // CATransform3D value for minimumRelativeValue CATransform3D transMinimumTiltAboutX = CATransform3DIdentity;