mirror of
https://github.com/status-im/react-native.git
synced 2025-01-10 09:35:48 +00:00
65513e501a
Summary: Explain the **motivation** for making this change. What existing problem does the pull request solve? This change is required when you try to set a focus on a view that doesn't exist and thus cannot be focused. In my specific use case, this occurred when trying to set a focus on a list item in a setInterval when the View (with the specific list item) had been popped. The while loop ran infinitely (eventually freezing the app) since the rootView doesn't exist. This adds that check and breaks out if so. All obj-c tests ran successfully. dlowder-salesforce Closes https://github.com/facebook/react-native/pull/12073 Differential Revision: D4468989 Pulled By: ericvicenti fbshipit-source-id: 7926c887035722c983c41cb6b6d9df567010c2ee
191 lines
7.2 KiB
Objective-C
191 lines
7.2 KiB
Objective-C
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*/
|
|
|
|
#import "RCTTVView.h"
|
|
|
|
#import "RCTAutoInsetsProtocol.h"
|
|
#import "RCTBorderDrawing.h"
|
|
#import "RCTBridge.h"
|
|
#import "RCTConvert.h"
|
|
#import "RCTEventDispatcher.h"
|
|
#import "RCTLog.h"
|
|
#import "RCTRootViewInternal.h"
|
|
#import "RCTTVNavigationEventEmitter.h"
|
|
#import "RCTUtils.h"
|
|
#import "RCTView.h"
|
|
#import "UIView+React.h"
|
|
|
|
@implementation RCTTVView
|
|
{
|
|
UITapGestureRecognizer *_selectRecognizer;
|
|
}
|
|
|
|
- (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
|
|
};
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused)
|
|
|
|
- (void)setIsTVSelectable:(BOOL)isTVSelectable {
|
|
self->_isTVSelectable = isTVSelectable;
|
|
if(isTVSelectable) {
|
|
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSelect:)];
|
|
recognizer.allowedPressTypes = @[@(UIPressTypeSelect)];
|
|
_selectRecognizer = recognizer;
|
|
[self addGestureRecognizer:_selectRecognizer];
|
|
} else {
|
|
if(_selectRecognizer) {
|
|
[self removeGestureRecognizer:_selectRecognizer];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)handleSelect:(UIGestureRecognizer *)r
|
|
{
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTTVNavigationEventNotification
|
|
object:@{@"eventType":@"select",@"tag":self.reactTag}];
|
|
}
|
|
|
|
- (BOOL)isUserInteractionEnabled
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)canBecomeFocused
|
|
{
|
|
return (self.isTVSelectable);
|
|
}
|
|
|
|
- (void)addParallaxMotionEffects
|
|
{
|
|
// Size of shift movements
|
|
CGFloat const shiftDistanceX = [self.tvParallaxProperties[@"shiftDistanceX"] floatValue];
|
|
CGFloat const shiftDistanceY = [self.tvParallaxProperties[@"shiftDistanceY"] floatValue];
|
|
|
|
// Make horizontal movements shift the centre left and right
|
|
UIInterpolatingMotionEffect *xShift = [[UIInterpolatingMotionEffect alloc]
|
|
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];
|
|
yShift.minimumRelativeValue = @( shiftDistanceY * -1.0f);
|
|
yShift.maximumRelativeValue = @( shiftDistanceY);
|
|
|
|
// Size of tilt movements
|
|
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];
|
|
|
|
// CATransform3D value for minimumRelativeValue
|
|
CATransform3D transMinimumTiltAboutY = CATransform3DIdentity;
|
|
transMinimumTiltAboutY.m34 = 1.0 / 500;
|
|
transMinimumTiltAboutY = CATransform3DRotate(transMinimumTiltAboutY, tiltAngle * -1.0, 0, 1, 0);
|
|
|
|
// CATransform3D value for minimumRelativeValue
|
|
CATransform3D transMaximumTiltAboutY = CATransform3DIdentity;
|
|
transMaximumTiltAboutY.m34 = 1.0 / 500;
|
|
transMaximumTiltAboutY = CATransform3DRotate(transMaximumTiltAboutY, tiltAngle, 0, 1, 0);
|
|
|
|
// Set the transform property boundaries for the interpolation
|
|
xTilt.minimumRelativeValue = [NSValue valueWithCATransform3D: transMinimumTiltAboutY];
|
|
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];
|
|
|
|
// CATransform3D value for minimumRelativeValue
|
|
CATransform3D transMinimumTiltAboutX = CATransform3DIdentity;
|
|
transMinimumTiltAboutX.m34 = 1.0 / 500;
|
|
transMinimumTiltAboutX = CATransform3DRotate(transMinimumTiltAboutX, tiltAngle * -1.0, 1, 0, 0);
|
|
|
|
// CATransform3D value for minimumRelativeValue
|
|
CATransform3D transMaximumTiltAboutX = CATransform3DIdentity;
|
|
transMaximumTiltAboutX.m34 = 1.0 / 500;
|
|
transMaximumTiltAboutX = CATransform3DRotate(transMaximumTiltAboutX, tiltAngle, 1, 0, 0);
|
|
|
|
// Set the transform property boundaries for the interpolation
|
|
yTilt.minimumRelativeValue = [NSValue valueWithCATransform3D: transMinimumTiltAboutX];
|
|
yTilt.maximumRelativeValue = [NSValue valueWithCATransform3D: transMaximumTiltAboutX];
|
|
|
|
// Add all of the motion effects to this group
|
|
self.motionEffects = @[xShift, yShift, xTilt, yTilt];
|
|
|
|
float magnification = [self.tvParallaxProperties[@"magnification"] floatValue];
|
|
|
|
[UIView animateWithDuration:0.2 animations:^{
|
|
self.transform = CGAffineTransformMakeScale(magnification, magnification);
|
|
}];
|
|
}
|
|
|
|
- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
|
|
{
|
|
if (context.nextFocusedView == self && self.isTVSelectable ) {
|
|
[self becomeFirstResponder];
|
|
[coordinator addCoordinatedAnimations:^(void){
|
|
if([self.tvParallaxProperties[@"enabled"] boolValue]) {
|
|
[self addParallaxMotionEffects];
|
|
}
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTTVNavigationEventNotification
|
|
object:@{@"eventType":@"focus",@"tag":self.reactTag}];
|
|
} completion:^(void){}];
|
|
} else {
|
|
[coordinator addCoordinatedAnimations:^(void){
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTTVNavigationEventNotification
|
|
object:@{@"eventType":@"blur",@"tag":self.reactTag}];
|
|
[UIView animateWithDuration:0.2 animations:^{
|
|
self.transform = CGAffineTransformMakeScale(1, 1);
|
|
}];
|
|
|
|
for (UIMotionEffect *effect in [self.motionEffects copy]){
|
|
[self removeMotionEffect:effect];
|
|
}
|
|
} completion:^(void){}];
|
|
[self resignFirstResponder];
|
|
}
|
|
}
|
|
|
|
- (void)setHasTVPreferredFocus:(BOOL)hasTVPreferredFocus
|
|
{
|
|
_hasTVPreferredFocus = hasTVPreferredFocus;
|
|
if (hasTVPreferredFocus) {
|
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
UIView *rootview = self;
|
|
while (![rootview isReactRootView] && rootview != nil) {
|
|
rootview = [rootview superview];
|
|
}
|
|
if (rootview == nil) return;
|
|
|
|
rootview = [rootview superview];
|
|
|
|
[(RCTRootView *)rootview setReactPreferredFocusedView:self];
|
|
[rootview setNeedsFocusUpdate];
|
|
[rootview updateFocusIfNeeded];
|
|
});
|
|
}
|
|
}
|
|
|
|
@end
|