RCTTVView.m 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import "RCTTVView.h"
  8. #import "RCTAutoInsetsProtocol.h"
  9. #import "RCTBorderDrawing.h"
  10. #import "RCTBridge.h"
  11. #import "RCTConvert.h"
  12. #import "RCTEventDispatcher.h"
  13. #import "RCTLog.h"
  14. #import "RCTRootViewInternal.h"
  15. #import "RCTTVNavigationEventEmitter.h"
  16. #import "RCTUtils.h"
  17. #import "RCTView.h"
  18. #import "UIView+React.h"
  19. @implementation RCTTVView {
  20. UITapGestureRecognizer *_selectRecognizer;
  21. }
  22. - (instancetype)initWithFrame:(CGRect)frame
  23. {
  24. if (self = [super initWithFrame:frame]) {
  25. dispatch_once(&onceToken, ^{
  26. defaultTVParallaxProperties = @{
  27. @"enabled" : @YES,
  28. @"shiftDistanceX" : @2.0f,
  29. @"shiftDistanceY" : @2.0f,
  30. @"tiltAngle" : @0.05f,
  31. @"magnification" : @1.0f,
  32. @"pressMagnification" : @1.0f,
  33. @"pressDuration" : @0.3f,
  34. @"pressDelay" : @0.0f
  35. };
  36. });
  37. self.tvParallaxProperties = defaultTVParallaxProperties;
  38. }
  39. return self;
  40. }
  41. static NSDictionary *defaultTVParallaxProperties = nil;
  42. static dispatch_once_t onceToken;
  43. - (void)setTvParallaxProperties:(NSDictionary *)tvParallaxProperties
  44. {
  45. if (_tvParallaxProperties == nil) {
  46. _tvParallaxProperties = [defaultTVParallaxProperties copy];
  47. return;
  48. }
  49. NSMutableDictionary *newParallaxProperties = [NSMutableDictionary dictionaryWithDictionary:_tvParallaxProperties];
  50. for (NSString *k in [defaultTVParallaxProperties allKeys]) {
  51. if (tvParallaxProperties[k]) {
  52. newParallaxProperties[k] = tvParallaxProperties[k];
  53. }
  54. }
  55. _tvParallaxProperties = [newParallaxProperties copy];
  56. }
  57. RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : unused)
  58. - (void)setIsTVSelectable:(BOOL)isTVSelectable
  59. {
  60. self->_isTVSelectable = isTVSelectable;
  61. if (isTVSelectable) {
  62. UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
  63. action:@selector(handleSelect:)];
  64. recognizer.allowedPressTypes = @[ @(UIPressTypeSelect) ];
  65. _selectRecognizer = recognizer;
  66. [self addGestureRecognizer:_selectRecognizer];
  67. } else {
  68. if (_selectRecognizer) {
  69. [self removeGestureRecognizer:_selectRecognizer];
  70. }
  71. }
  72. }
  73. - (void)handleSelect:(__unused UIGestureRecognizer *)r
  74. {
  75. if ([self.tvParallaxProperties[@"enabled"] boolValue] == YES) {
  76. float magnification = [self.tvParallaxProperties[@"magnification"] floatValue];
  77. float pressMagnification = [self.tvParallaxProperties[@"pressMagnification"] floatValue];
  78. // Duration of press animation
  79. float pressDuration = [self.tvParallaxProperties[@"pressDuration"] floatValue];
  80. // Delay of press animation
  81. float pressDelay = [self.tvParallaxProperties[@"pressDelay"] floatValue];
  82. [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:pressDelay]];
  83. [UIView animateWithDuration:(pressDuration / 2)
  84. animations:^{
  85. self.transform = CGAffineTransformMakeScale(pressMagnification, pressMagnification);
  86. }
  87. completion:^(__unused BOOL finished1) {
  88. [UIView animateWithDuration:(pressDuration / 2)
  89. animations:^{
  90. self.transform = CGAffineTransformMakeScale(magnification, magnification);
  91. }
  92. completion:^(__unused BOOL finished2) {
  93. [[NSNotificationCenter defaultCenter]
  94. postNotificationName:RCTTVNavigationEventNotification
  95. object:@{@"eventType" : @"select", @"tag" : self.reactTag}];
  96. }];
  97. }];
  98. } else {
  99. [[NSNotificationCenter defaultCenter] postNotificationName:RCTTVNavigationEventNotification
  100. object:@{@"eventType" : @"select", @"tag" : self.reactTag}];
  101. }
  102. }
  103. - (BOOL)isUserInteractionEnabled
  104. {
  105. return YES;
  106. }
  107. - (BOOL)canBecomeFocused
  108. {
  109. return (self.isTVSelectable);
  110. }
  111. - (void)addParallaxMotionEffects
  112. {
  113. // Size of shift movements
  114. CGFloat const shiftDistanceX = [self.tvParallaxProperties[@"shiftDistanceX"] floatValue];
  115. CGFloat const shiftDistanceY = [self.tvParallaxProperties[@"shiftDistanceY"] floatValue];
  116. // Make horizontal movements shift the centre left and right
  117. UIInterpolatingMotionEffect *xShift =
  118. [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.x"
  119. type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
  120. xShift.minimumRelativeValue = @(shiftDistanceX * -1.0f);
  121. xShift.maximumRelativeValue = @(shiftDistanceX);
  122. // Make vertical movements shift the centre up and down
  123. UIInterpolatingMotionEffect *yShift =
  124. [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"center.y"
  125. type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
  126. yShift.minimumRelativeValue = @(shiftDistanceY * -1.0f);
  127. yShift.maximumRelativeValue = @(shiftDistanceY);
  128. // Size of tilt movements
  129. CGFloat const tiltAngle = [self.tvParallaxProperties[@"tiltAngle"] floatValue];
  130. // Now make horizontal movements effect a rotation about the Y axis for side-to-side rotation.
  131. UIInterpolatingMotionEffect *xTilt =
  132. [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"layer.transform"
  133. type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
  134. // CATransform3D value for minimumRelativeValue
  135. CATransform3D transMinimumTiltAboutY = CATransform3DIdentity;
  136. transMinimumTiltAboutY.m34 = 1.0 / 500;
  137. transMinimumTiltAboutY = CATransform3DRotate(transMinimumTiltAboutY, tiltAngle * -1.0, 0, 1, 0);
  138. // CATransform3D value for minimumRelativeValue
  139. CATransform3D transMaximumTiltAboutY = CATransform3DIdentity;
  140. transMaximumTiltAboutY.m34 = 1.0 / 500;
  141. transMaximumTiltAboutY = CATransform3DRotate(transMaximumTiltAboutY, tiltAngle, 0, 1, 0);
  142. // Set the transform property boundaries for the interpolation
  143. xTilt.minimumRelativeValue = [NSValue valueWithCATransform3D:transMinimumTiltAboutY];
  144. xTilt.maximumRelativeValue = [NSValue valueWithCATransform3D:transMaximumTiltAboutY];
  145. // Now make vertical movements effect a rotation about the X axis for up and down rotation.
  146. UIInterpolatingMotionEffect *yTilt =
  147. [[UIInterpolatingMotionEffect alloc] initWithKeyPath:@"layer.transform"
  148. type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
  149. // CATransform3D value for minimumRelativeValue
  150. CATransform3D transMinimumTiltAboutX = CATransform3DIdentity;
  151. transMinimumTiltAboutX.m34 = 1.0 / 500;
  152. transMinimumTiltAboutX = CATransform3DRotate(transMinimumTiltAboutX, tiltAngle * -1.0, 1, 0, 0);
  153. // CATransform3D value for minimumRelativeValue
  154. CATransform3D transMaximumTiltAboutX = CATransform3DIdentity;
  155. transMaximumTiltAboutX.m34 = 1.0 / 500;
  156. transMaximumTiltAboutX = CATransform3DRotate(transMaximumTiltAboutX, tiltAngle, 1, 0, 0);
  157. // Set the transform property boundaries for the interpolation
  158. yTilt.minimumRelativeValue = [NSValue valueWithCATransform3D:transMinimumTiltAboutX];
  159. yTilt.maximumRelativeValue = [NSValue valueWithCATransform3D:transMaximumTiltAboutX];
  160. // Add all of the motion effects to this group
  161. self.motionEffects = @[ xShift, yShift, xTilt, yTilt ];
  162. float magnification = [self.tvParallaxProperties[@"magnification"] floatValue];
  163. [UIView animateWithDuration:0.2
  164. animations:^{
  165. self.transform = CGAffineTransformMakeScale(magnification, magnification);
  166. }];
  167. }
  168. - (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context
  169. withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
  170. {
  171. if (context.nextFocusedView == self && self.isTVSelectable) {
  172. [self becomeFirstResponder];
  173. [coordinator
  174. addCoordinatedAnimations:^(void) {
  175. if ([self.tvParallaxProperties[@"enabled"] boolValue]) {
  176. [self addParallaxMotionEffects];
  177. }
  178. [[NSNotificationCenter defaultCenter]
  179. postNotificationName:RCTTVNavigationEventNotification
  180. object:@{@"eventType" : @"focus", @"tag" : self.reactTag}];
  181. }
  182. completion:^(void){
  183. }];
  184. } else {
  185. [coordinator
  186. addCoordinatedAnimations:^(void) {
  187. [[NSNotificationCenter defaultCenter] postNotificationName:RCTTVNavigationEventNotification
  188. object:@{@"eventType" : @"blur", @"tag" : self.reactTag}];
  189. [UIView animateWithDuration:0.2
  190. animations:^{
  191. self.transform = CGAffineTransformMakeScale(1, 1);
  192. }];
  193. for (UIMotionEffect *effect in [self.motionEffects copy]) {
  194. [self removeMotionEffect:effect];
  195. }
  196. }
  197. completion:^(void){
  198. }];
  199. [self resignFirstResponder];
  200. }
  201. }
  202. - (void)setHasTVPreferredFocus:(BOOL)hasTVPreferredFocus
  203. {
  204. _hasTVPreferredFocus = hasTVPreferredFocus;
  205. if (hasTVPreferredFocus) {
  206. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  207. UIView *rootview = self;
  208. while (![rootview isReactRootView] && rootview != nil) {
  209. rootview = [rootview superview];
  210. }
  211. if (rootview == nil)
  212. return;
  213. rootview = [rootview superview];
  214. [rootview setNeedsFocusUpdate];
  215. [rootview updateFocusIfNeeded];
  216. });
  217. }
  218. }
  219. @end