react-native-navigation的迁移库

RCCNotification.m 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. #import "RCCNotification.h"
  2. #import <React/RCTRootView.h>
  3. #import "RCTHelpers.h"
  4. @interface NotificationView : UIView
  5. @property (nonatomic, strong) RCTRootView *reactView;
  6. @property (nonatomic, strong) UIView *slideAnimGapView;
  7. @property (nonatomic, strong) NSDictionary *params;
  8. @property (nonatomic, strong) NSTimer *autoDismissTimer;
  9. @property (nonatomic) BOOL yellowBoxRemoved;
  10. @end
  11. @implementation NotificationView
  12. -(id)initWithParams:(NSDictionary*)params
  13. {
  14. self = [super initWithFrame:CGRectZero];
  15. if (self)
  16. {
  17. self.params = params;
  18. self.yellowBoxRemoved = NO;
  19. self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  20. self.reactView = [[RCTRootView alloc] initWithBridge:[[RCCManager sharedInstance] getBridge] moduleName:params[@"component"] initialProperties:params[@"passProps"]];
  21. self.reactView.backgroundColor = [UIColor clearColor];
  22. self.reactView.sizeFlexibility = RCTRootViewSizeFlexibilityWidthAndHeight;
  23. self.reactView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
  24. [self addSubview:self.reactView];
  25. [self.reactView.contentView.layer addObserver:self forKeyPath:@"frame" options:0 context:nil];
  26. [self.reactView.contentView.layer addObserver:self forKeyPath:@"bounds" options:0 context:NULL];
  27. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onRNReload) name:RCTJavaScriptWillStartLoadingNotification object:nil];
  28. if ([params[@"dismissWithSwipe"] boolValue])
  29. {
  30. UISwipeGestureRecognizer *swipeGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(performDismiss)];
  31. swipeGesture.direction = [self swipeDirection];
  32. [self addGestureRecognizer:swipeGesture];
  33. }
  34. if (params[@"shadowRadius"] != nil && [params[@"shadowRadius"] floatValue] > 0)
  35. {
  36. self.layer.shadowColor = [UIColor blackColor].CGColor;
  37. self.layer.shadowOffset = CGSizeMake(0, 0);
  38. self.layer.shadowRadius = [params[@"shadowRadius"] floatValue];
  39. self.layer.shadowOpacity = 0.6;
  40. }
  41. self.hidden = YES;
  42. }
  43. return self;
  44. }
  45. -(void)layoutSubviews
  46. {
  47. [super layoutSubviews];
  48. if(!self.yellowBoxRemoved)
  49. {
  50. self.yellowBoxRemoved = [RCTHelpers removeYellowBox:self.reactView];
  51. }
  52. }
  53. -(UISwipeGestureRecognizerDirection)swipeDirection
  54. {
  55. UISwipeGestureRecognizerDirection direction = [self isBottomPosition] ? UISwipeGestureRecognizerDirectionDown : UISwipeGestureRecognizerDirectionUp;
  56. NSString *animationType = [self.params valueForKeyPath:@"animation.type"];
  57. if ([animationType isEqualToString:@"slide-left"])
  58. direction = UISwipeGestureRecognizerDirectionRight;
  59. else if ([animationType isEqualToString:@"slide-right"])
  60. direction = UISwipeGestureRecognizerDirectionLeft;
  61. return direction;
  62. }
  63. -(void)killAutoDismissTimer
  64. {
  65. if (self.autoDismissTimer != nil)
  66. {
  67. [self.autoDismissTimer invalidate];
  68. self.autoDismissTimer = nil;
  69. }
  70. }
  71. -(void)removeObservers
  72. {
  73. [[NSNotificationCenter defaultCenter] removeObserver:self];
  74. [self.reactView.contentView.layer removeObserver:self forKeyPath:@"frame" context:nil];
  75. [self.reactView.contentView.layer removeObserver:self forKeyPath:@"bounds" context:NULL];
  76. }
  77. -(void)cleanup
  78. {
  79. [self removeObservers];
  80. [self killAutoDismissTimer];
  81. }
  82. -(void)dealloc
  83. {
  84. [self cleanup];
  85. }
  86. -(void)onRNReload
  87. {
  88. [self cleanup];
  89. [self removeFromSuperview];
  90. self.reactView = nil;
  91. }
  92. -(UIColor*)reactViewAvgColor
  93. {
  94. if (self.reactView.contentView.frame.size.width == 0 || self.reactView.contentView.frame.size.height == 0)
  95. {
  96. return [UIColor clearColor];
  97. }
  98. UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 0);
  99. [self.reactView.contentView drawViewHierarchyInRect:CGRectMake(0, 0, 1, 1) afterScreenUpdates:NO];
  100. UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
  101. UIGraphicsEndImageContext();
  102. CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
  103. const UInt8* data = CFDataGetBytePtr(pixelData);
  104. CFRelease(pixelData);
  105. //after scale defaults to bgr
  106. CGFloat red = data[2] / 255.0f,
  107. green = data[1] / 255.0f,
  108. blue = data[0] / 255.0f,
  109. alpha = data[3] / 255.0f;
  110. UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
  111. return color;
  112. }
  113. -(BOOL)shouldAddSlidingAnimGapView
  114. {
  115. if (![[self.params valueForKeyPath:@"animation.animated"] boolValue])
  116. {
  117. return NO;
  118. }
  119. NSString *animationType = [self.params valueForKeyPath:@"animation.type"];
  120. if (![animationType isEqualToString:@"slide-down"])
  121. {
  122. return NO;
  123. }
  124. if (![self.params valueForKeyPath:@"animation.damping"])
  125. {
  126. return NO;
  127. }
  128. CGFloat damping = [[self.params valueForKeyPath:@"animation.damping"] floatValue];
  129. if (damping >= 1)
  130. {
  131. return NO;
  132. }
  133. return YES;
  134. }
  135. -(BOOL)isBottomPosition
  136. {
  137. return ((self.params[@"position"] != nil) && ([self.params[@"position"] isEqualToString:@"bottom"]));
  138. }
  139. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
  140. {
  141. CGSize frameSize = CGSizeZero;
  142. if ([object isKindOfClass:[CALayer class]])
  143. frameSize = ((CALayer*)object).frame.size;
  144. if ([object isKindOfClass:[UIView class]])
  145. frameSize = ((UIView*)object).frame.size;
  146. if (!CGSizeEqualToSize(frameSize, self.reactView.frame.size))
  147. {
  148. BOOL isBottomPosition = [self isBottomPosition];
  149. CGFloat yPos = isBottomPosition ? self.superview.bounds.size.height - frameSize.height : 0;
  150. self.frame = CGRectMake((self.superview.frame.size.width - frameSize.width) / 2.0, yPos, frameSize.width, frameSize.height);
  151. self.reactView.frame = CGRectMake(0, 0, frameSize.width, frameSize.height);
  152. self.reactView.contentView.frame = CGRectMake(0, 0, frameSize.width, frameSize.height);
  153. //if necessary, add a view with the same color to cover the gap if there's a slide animation with spring
  154. if ([self shouldAddSlidingAnimGapView])
  155. {
  156. if (self.slideAnimGapView == nil)
  157. {
  158. self.slideAnimGapView = [[UIView alloc] initWithFrame:CGRectZero];
  159. [self.reactView addSubview:self.slideAnimGapView];
  160. }
  161. CGFloat yPos = isBottomPosition ? frameSize.height : -20;
  162. self.slideAnimGapView.frame = CGRectMake(0, yPos, self.superview.frame.size.width, 20);
  163. dispatch_async(dispatch_get_main_queue(), ^
  164. {
  165. self.slideAnimGapView.backgroundColor = [self reactViewAvgColor];
  166. });
  167. }
  168. if (self.params[@"shadowRadius"] != nil && [self.params[@"shadowRadius"] floatValue] > 0)
  169. {
  170. self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
  171. }
  172. }
  173. }
  174. -(void)performDismiss
  175. {
  176. [RCCNotification dismissWithParams:self.params resolver:nil rejecter:nil];
  177. }
  178. -(void)startAutoDismissTimerIfNecessary
  179. {
  180. if (self.params[@"autoDismissTimerSec"])
  181. {
  182. [self killAutoDismissTimer];
  183. CGFloat interval = [self.params[@"autoDismissTimerSec"] floatValue];
  184. interval = MAX(interval, 1);
  185. self.autoDismissTimer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(performDismiss) userInfo:nil repeats:NO];
  186. }
  187. }
  188. -(void)applyAnimations
  189. {
  190. NSString *animationType = [self.params valueForKeyPath:@"animation.type"];
  191. if ([animationType isEqualToString:@"swing"])
  192. {
  193. CATransform3D transform = CATransform3DIdentity;
  194. transform.m34 = -0.002f;
  195. transform.m24 = -0.007f;
  196. if (self.layer.anchorPoint.x == 0.5 && self.layer.anchorPoint.y == 0.5)
  197. {
  198. CGPoint anchorPoint = CGPointMake(0.5, 0);
  199. CGFloat yOffset = -self.layer.frame.size.height / 2.0;
  200. if([self isBottomPosition])
  201. {
  202. anchorPoint = CGPointMake(0.5, 1);
  203. yOffset = self.layer.frame.size.height / 2.0;
  204. }
  205. self.layer.anchorPoint = anchorPoint;
  206. self.layer.frame = CGRectOffset(self.layer.frame, 0, yOffset);
  207. }
  208. self.layer.zPosition = 1000;
  209. self.layer.transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0);
  210. }
  211. else
  212. {
  213. int slideAnimationSign = [self isBottomPosition] ? 1 : -1;
  214. CGAffineTransform transform = CGAffineTransformIdentity;
  215. if ([animationType isEqualToString:@"slide-down"])
  216. transform = CGAffineTransformMakeTranslation(0, slideAnimationSign * self.frame.size.height);
  217. else if ([animationType isEqualToString:@"slide-left"])
  218. transform = CGAffineTransformMakeTranslation(self.frame.size.width, 0);
  219. else if ([animationType isEqualToString:@"slide-right"])
  220. transform = CGAffineTransformMakeTranslation(-self.frame.size.width, 0);
  221. self.transform = transform;
  222. }
  223. if ( [[self.params valueForKeyPath:@"animation.fade"] boolValue])
  224. {
  225. self.alpha = 0;
  226. }
  227. }
  228. #pragma mark - show/dismiss
  229. -(void)showAnimationEnded:(void (^)(void))completion
  230. {
  231. self.alpha = 1;
  232. if (completion)
  233. {
  234. completion();
  235. }
  236. [self startAutoDismissTimerIfNecessary];
  237. }
  238. -(void)dismissAnimationEnded:(void (^)(void))completion
  239. {
  240. if (completion)
  241. {
  242. completion();
  243. }
  244. [self removeFromSuperview];
  245. }
  246. -(void)performShowWithCompletion:(void (^)(void))completion
  247. {
  248. self.hidden = NO;
  249. if ([[self.params valueForKeyPath:@"animation.animated"] boolValue])
  250. {
  251. CGFloat duration = [[self.params valueForKeyPath:@"animation.duration"] floatValue];
  252. CGFloat delay = [[self.params valueForKeyPath:@"animation.delay"] floatValue];
  253. CGFloat damping = 1;
  254. if ([self.params valueForKeyPath:@"animation.damping"] != nil)
  255. {
  256. damping = [[self.params valueForKeyPath:@"animation.damping"] floatValue];
  257. damping = MAX(damping, 0);
  258. damping = MIN(damping, 1);
  259. }
  260. [self applyAnimations];
  261. [UIView animateWithDuration:duration delay:delay usingSpringWithDamping:damping initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseOut animations:^()
  262. {
  263. self.alpha = 1;
  264. self.transform = CGAffineTransformIdentity;
  265. self.layer.transform = CATransform3DIdentity;
  266. }
  267. completion:^(BOOL finished)
  268. {
  269. [self showAnimationEnded:completion];
  270. }];
  271. }
  272. else
  273. {
  274. [self showAnimationEnded:completion];
  275. }
  276. }
  277. -(void)showWithCompletion:(void (^)(void))completion
  278. {
  279. if (self.frame.size.height == 0 || self.frame.size.width == 0)
  280. {
  281. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)),
  282. dispatch_get_main_queue(),
  283. ^{
  284. [self showWithCompletion:completion];
  285. });
  286. }
  287. else
  288. {
  289. [self performShowWithCompletion:completion];
  290. }
  291. }
  292. -(void)dismissWithCompletion:(void (^)(void))completion
  293. {
  294. [self killAutoDismissTimer];
  295. [self.reactView cancelTouches];
  296. if ([[self.params valueForKeyPath:@"animation.animated"] boolValue])
  297. {
  298. CGFloat duration = [[self.params valueForKeyPath:@"animation.duration"] floatValue] * 0.75;
  299. [UIView animateWithDuration:duration delay:0 usingSpringWithDamping:1 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseIn
  300. animations:^()
  301. {
  302. [self applyAnimations];
  303. }
  304. completion:^(BOOL finished)
  305. {
  306. [self dismissAnimationEnded:completion];
  307. }];
  308. }
  309. else
  310. {
  311. [self dismissAnimationEnded:completion];
  312. }
  313. }
  314. @end
  315. @interface PendingNotification : NSObject
  316. @property (nonatomic, copy) void (^resolve)(id result);
  317. @property (nonatomic, copy) void (^reject)(NSString *code, NSString *message, NSError *error);
  318. @property (nonatomic, strong) NSDictionary *params;
  319. @end
  320. @implementation PendingNotification
  321. @end
  322. @implementation RCCNotification
  323. static NSTimeInterval gLastShownTime = NSTimeIntervalSince1970;
  324. static NSMutableArray *gPendingNotifications = nil;
  325. static NSMutableArray *gShownNotificationViews = nil;
  326. +(void)showWithParams:(NSDictionary*)params resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject
  327. {
  328. if(gPendingNotifications == nil)
  329. gPendingNotifications = [NSMutableArray array];
  330. if(gShownNotificationViews == nil)
  331. gShownNotificationViews = [NSMutableArray array];
  332. if ([gShownNotificationViews count] > 0)
  333. {
  334. for (NotificationView *notificationView in gShownNotificationViews)
  335. {
  336. [notificationView killAutoDismissTimer];
  337. }
  338. //limit the amount of consecutive notifications per second. If they arrive too fast, the last one will be remembered as pending
  339. if(CFAbsoluteTimeGetCurrent() - gLastShownTime < 1)
  340. {
  341. PendingNotification *pendingNotification = [PendingNotification new];
  342. pendingNotification.params = params;
  343. pendingNotification.resolve = resolve;
  344. pendingNotification.reject = reject;
  345. [gPendingNotifications addObject:pendingNotification];
  346. return;
  347. }
  348. }
  349. gLastShownTime = CFAbsoluteTimeGetCurrent();
  350. UIWindow *window = [[RCCManager sharedInstance] getAppWindow];
  351. NotificationView *notificationView = [[NotificationView alloc] initWithParams:params];
  352. [window addSubview:notificationView];
  353. [gShownNotificationViews addObject:notificationView];
  354. [notificationView showWithCompletion:^()
  355. {
  356. if (resolve)
  357. {
  358. resolve(nil);
  359. }
  360. }];
  361. }
  362. +(void)dismissWithParams:(NSDictionary*)params resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject
  363. {
  364. int count = [gShownNotificationViews count];
  365. for (int i = count - 1 ; i >= 0; i--)
  366. {
  367. NotificationView *notificationView = [gShownNotificationViews objectAtIndex:i];
  368. [gShownNotificationViews removeObject:notificationView];
  369. [notificationView dismissWithCompletion:^()
  370. {
  371. if (i == (count - 1))
  372. {
  373. if(resolve)
  374. {
  375. resolve(nil);
  376. }
  377. if ([gPendingNotifications count] > 0)
  378. {
  379. PendingNotification *pendingNotification = [gPendingNotifications lastObject];
  380. [self showWithParams:pendingNotification.params resolver:pendingNotification.resolve rejecter:pendingNotification.reject];
  381. [gPendingNotifications removeLastObject];
  382. }
  383. }
  384. }];
  385. }
  386. }
  387. @end