diff --git a/React/Modules/RCTLayoutAnimation.h b/React/Modules/RCTLayoutAnimation.h new file mode 100644 index 000000000..b1cdb782f --- /dev/null +++ b/React/Modules/RCTLayoutAnimation.h @@ -0,0 +1,38 @@ +/** + * 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 + +#import + +@interface RCTLayoutAnimation : NSObject + +@property (nonatomic, readonly) NSTimeInterval duration; +@property (nonatomic, readonly) NSTimeInterval delay; +@property (nonatomic, readonly, copy) NSString *property; +@property (nonatomic, readonly) CGFloat springDamping; +@property (nonatomic, readonly) CGFloat initialVelocity; +@property (nonatomic, readonly) RCTAnimationType animationType; + ++ (void)initializeStatics; + +- (instancetype)initWithDuration:(NSTimeInterval)duration + delay:(NSTimeInterval)delay + property:(NSString *)property + springDamping:(CGFloat)springDamping + initialVelocity:(CGFloat)initialVelocity + animationType:(RCTAnimationType)animationType; + +- (instancetype)initWithDuration:(NSTimeInterval)duration + config:(NSDictionary *)config; + +- (void)performAnimations:(void (^)(void))animations + withCompletionBlock:(void (^)(BOOL completed))completionBlock; + +@end diff --git a/React/Modules/RCTLayoutAnimation.m b/React/Modules/RCTLayoutAnimation.m new file mode 100644 index 000000000..3d3bf3b5c --- /dev/null +++ b/React/Modules/RCTLayoutAnimation.m @@ -0,0 +1,155 @@ +/** + * 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 "RCTLayoutAnimation.h" + +#import "RCTConvert.h" + +@implementation RCTLayoutAnimation + +static UIViewAnimationCurve _currentKeyboardAnimationCurve; + +static UIViewAnimationOptions UIViewAnimationOptionsFromRCTAnimationType(RCTAnimationType type) +{ + switch (type) { + case RCTAnimationTypeLinear: + return UIViewAnimationOptionCurveLinear; + case RCTAnimationTypeEaseIn: + return UIViewAnimationOptionCurveEaseIn; + case RCTAnimationTypeEaseOut: + return UIViewAnimationOptionCurveEaseOut; + case RCTAnimationTypeEaseInEaseOut: + return UIViewAnimationOptionCurveEaseInOut; + case RCTAnimationTypeKeyboard: + // http://stackoverflow.com/questions/18870447/how-to-use-the-default-ios7-uianimation-curve + return (UIViewAnimationOptions)(_currentKeyboardAnimationCurve << 16); + default: + RCTLogError(@"Unsupported animation type %zd", type); + return UIViewAnimationOptionCurveEaseInOut; + } +} + +// Use a custom initialization function rather than implementing `+initialize` so that we can control +// when the initialization code runs. `+initialize` runs immediately before the first message is sent +// to the class which may be too late for us. By this time, we may have missed some +// `UIKeyboardWillChangeFrameNotification`s. ++ (void)initializeStatics +{ +#if !TARGET_OS_TV + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(keyboardWillChangeFrame:) + name:UIKeyboardWillChangeFrameNotification + object:nil]; + }); +#endif +} + ++ (void)keyboardWillChangeFrame:(NSNotification *)notification +{ +#if !TARGET_OS_TV + NSDictionary *userInfo = notification.userInfo; + _currentKeyboardAnimationCurve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; +#endif +} + +- (instancetype)initWithDuration:(NSTimeInterval)duration + delay:(NSTimeInterval)delay + property:(NSString *)property + springDamping:(CGFloat)springDamping + initialVelocity:(CGFloat)initialVelocity + animationType:(RCTAnimationType)animationType +{ + if (self = [super init]) { + _duration = duration; + _delay = delay; + _property = property; + _springDamping = springDamping; + _initialVelocity = initialVelocity; + _animationType = animationType; + } + + return self; +} + +- (instancetype)initWithDuration:(NSTimeInterval)duration + config:(NSDictionary *)config +{ + if (!config) { + return nil; + } + + if (self = [super init]) { + _property = [RCTConvert NSString:config[@"property"]]; + + _duration = [RCTConvert NSTimeInterval:config[@"duration"]] ?: duration; + if (_duration > 0.0 && _duration < 0.01) { + RCTLogError(@"RCTLayoutAnimationGroup expects timings to be in ms, not seconds."); + _duration = _duration * 1000.0; + } + + _delay = [RCTConvert NSTimeInterval:config[@"delay"]]; + if (_delay > 0.0 && _delay < 0.01) { + RCTLogError(@"RCTLayoutAnimationGroup expects timings to be in ms, not seconds."); + _delay = _delay * 1000.0; + } + + _animationType = [RCTConvert RCTAnimationType:config[@"type"]]; + if (_animationType == RCTAnimationTypeSpring) { + _springDamping = [RCTConvert CGFloat:config[@"springDamping"]]; + _initialVelocity = [RCTConvert CGFloat:config[@"initialVelocity"]]; + } + } + + return self; +} + +- (void)performAnimations:(void (^)(void))animations + withCompletionBlock:(void (^)(BOOL completed))completionBlock +{ + if (_animationType == RCTAnimationTypeSpring) { + [UIView animateWithDuration:_duration + delay:_delay + usingSpringWithDamping:_springDamping + initialSpringVelocity:_initialVelocity + options:UIViewAnimationOptionBeginFromCurrentState + animations:animations + completion:completionBlock]; + } else { + UIViewAnimationOptions options = + UIViewAnimationOptionBeginFromCurrentState | + UIViewAnimationOptionsFromRCTAnimationType(_animationType); + + [UIView animateWithDuration:_duration + delay:_delay + options:options + animations:animations + completion:completionBlock]; + } +} + +- (BOOL)isEqual:(RCTLayoutAnimation *)animation +{ + return + _duration == animation.duration && + _delay == animation.delay && + (_property == animation.property || [_property isEqualToString:animation.property]) && + _springDamping == animation.springDamping && + _initialVelocity == animation.initialVelocity && + _animationType == animation.animationType; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p; duration: %f; delay: %f; property: %@; springDamping: %f; initialVelocity: %f; animationType: %li;>", + NSStringFromClass([self class]), self, _duration, _delay, _property, _springDamping, _initialVelocity, (long)_animationType]; +} + +@end diff --git a/React/Modules/RCTLayoutAnimationGroup.h b/React/Modules/RCTLayoutAnimationGroup.h new file mode 100644 index 000000000..651e28727 --- /dev/null +++ b/React/Modules/RCTLayoutAnimationGroup.h @@ -0,0 +1,32 @@ +/** + * 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 + +#import + +@class RCTLayoutAnimation; + +@interface RCTLayoutAnimationGroup : NSObject + +@property (nonatomic, readonly) RCTLayoutAnimation *creatingLayoutAnimation; +@property (nonatomic, readonly) RCTLayoutAnimation *updatingLayoutAnimation; +@property (nonatomic, readonly) RCTLayoutAnimation *deletingLayoutAnimation; + +@property (nonatomic, copy) RCTResponseSenderBlock callback; + +- (instancetype)initWithCreatingLayoutAnimation:(RCTLayoutAnimation *)creatingLayoutAnimation + updatingLayoutAnimation:(RCTLayoutAnimation *)updatingLayoutAnimation + deletingLayoutAnimation:(RCTLayoutAnimation *)deletingLayoutAnimation + callback:(RCTResponseSenderBlock)callback; + +- (instancetype)initWithConfig:(NSDictionary *)config + callback:(RCTResponseSenderBlock)callback; + +@end diff --git a/React/Modules/RCTLayoutAnimationGroup.m b/React/Modules/RCTLayoutAnimationGroup.m new file mode 100644 index 000000000..c85b25f94 --- /dev/null +++ b/React/Modules/RCTLayoutAnimationGroup.m @@ -0,0 +1,74 @@ +/** + * 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 "RCTLayoutAnimationGroup.h" + +#import "RCTLayoutAnimation.h" +#import "RCTConvert.h" + +@implementation RCTLayoutAnimationGroup + +- (instancetype)initWithCreatingLayoutAnimation:(RCTLayoutAnimation *)creatingLayoutAnimation + updatingLayoutAnimation:(RCTLayoutAnimation *)updatingLayoutAnimation + deletingLayoutAnimation:(RCTLayoutAnimation *)deletingLayoutAnimation + callback:(RCTResponseSenderBlock)callback +{ + if (self = [super init]) { + _creatingLayoutAnimation = creatingLayoutAnimation; + _updatingLayoutAnimation = updatingLayoutAnimation; + _deletingLayoutAnimation = deletingLayoutAnimation; + _callback = callback; + } + + return self; +} + +- (instancetype)initWithConfig:(NSDictionary *)config + callback:(RCTResponseSenderBlock)callback +{ + if (!config) { + return nil; + } + + if (self = [super init]) { + NSTimeInterval duration = [RCTConvert NSTimeInterval:config[@"duration"]]; + + if (duration > 0.0 && duration < 0.01) { + RCTLogError(@"RCTLayoutAnimationGroup expects timings to be in ms, not seconds."); + duration = duration * 1000.0; + } + + _creatingLayoutAnimation = [[RCTLayoutAnimation alloc] initWithDuration:duration config:config[@"create"]]; + _updatingLayoutAnimation = [[RCTLayoutAnimation alloc] initWithDuration:duration config:config[@"update"]]; + _deletingLayoutAnimation = [[RCTLayoutAnimation alloc] initWithDuration:duration config:config[@"delete"]]; + _callback = callback; + } + + return self; +} + +- (BOOL)isEqual:(RCTLayoutAnimationGroup *)layoutAnimation +{ + RCTLayoutAnimation *creatingLayoutAnimation = layoutAnimation.creatingLayoutAnimation; + RCTLayoutAnimation *updatingLayoutAnimation = layoutAnimation.updatingLayoutAnimation; + RCTLayoutAnimation *deletingLayoutAnimation = layoutAnimation.deletingLayoutAnimation; + + return + (_creatingLayoutAnimation == creatingLayoutAnimation || [_creatingLayoutAnimation isEqual:creatingLayoutAnimation]) && + (_updatingLayoutAnimation == updatingLayoutAnimation || [_updatingLayoutAnimation isEqual:updatingLayoutAnimation]) && + (_deletingLayoutAnimation == deletingLayoutAnimation || [_deletingLayoutAnimation isEqual:deletingLayoutAnimation]); +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p; creatingLayoutAnimation: %@; updatingLayoutAnimation: %@; deletingLayoutAnimation: %@>", + NSStringFromClass([self class]), self, [_creatingLayoutAnimation description], [_updatingLayoutAnimation description], [_deletingLayoutAnimation description]]; +} + +@end diff --git a/React/Modules/RCTUIManager.h b/React/Modules/RCTUIManager.h index d59b6e336..3e3a18035 100644 --- a/React/Modules/RCTUIManager.h +++ b/React/Modules/RCTUIManager.h @@ -59,6 +59,7 @@ RCT_EXTERN NSString *const RCTUIManagerDidRemoveRootViewNotification; */ RCT_EXTERN NSString *const RCTUIManagerRootViewKey; +@class RCTLayoutAnimationGroup; @class RCTUIManagerObserverCoordinator; /** @@ -115,6 +116,13 @@ RCT_EXTERN NSString *const RCTUIManagerRootViewKey; */ - (void)setBackgroundColor:(UIColor *)color forView:(UIView *)view; +/** + * Sets up layout animation which will perform on next layout pass. + * The animation will affect only one next layout pass. + * Must be called on the main queue. + */ +- (void)setNextLayoutAnimationGroup:(RCTLayoutAnimationGroup *)layoutAnimationGroup; + /** * Schedule a block to be executed on the UI thread. Useful if you need to execute * view logic after all currently queued view updates have completed. diff --git a/React/Modules/RCTUIManager.m b/React/Modules/RCTUIManager.m index 5d5c36a40..14c377e1c 100644 --- a/React/Modules/RCTUIManager.m +++ b/React/Modules/RCTUIManager.m @@ -23,6 +23,8 @@ #import "RCTConvert.h" #import "RCTDefines.h" #import "RCTEventDispatcher.h" +#import "RCTLayoutAnimation.h" +#import "RCTLayoutAnimationGroup.h" #import "RCTLog.h" #import "RCTModuleData.h" #import "RCTModuleMethod.h" @@ -55,160 +57,6 @@ NSString *const RCTUIManagerDidRegisterRootViewNotification = @"RCTUIManagerDidR NSString *const RCTUIManagerDidRemoveRootViewNotification = @"RCTUIManagerDidRemoveRootViewNotification"; NSString *const RCTUIManagerRootViewKey = @"RCTUIManagerRootViewKey"; -@interface RCTAnimation : NSObject - -@property (nonatomic, readonly) NSTimeInterval duration; -@property (nonatomic, readonly) NSTimeInterval delay; -@property (nonatomic, readonly, copy) NSString *property; -@property (nonatomic, readonly) CGFloat springDamping; -@property (nonatomic, readonly) CGFloat initialVelocity; -@property (nonatomic, readonly) RCTAnimationType animationType; - -@end - -static UIViewAnimationCurve _currentKeyboardAnimationCurve; - -@implementation RCTAnimation - -static UIViewAnimationOptions UIViewAnimationOptionsFromRCTAnimationType(RCTAnimationType type) -{ - switch (type) { - case RCTAnimationTypeLinear: - return UIViewAnimationOptionCurveLinear; - case RCTAnimationTypeEaseIn: - return UIViewAnimationOptionCurveEaseIn; - case RCTAnimationTypeEaseOut: - return UIViewAnimationOptionCurveEaseOut; - case RCTAnimationTypeEaseInEaseOut: - return UIViewAnimationOptionCurveEaseInOut; - case RCTAnimationTypeKeyboard: - // http://stackoverflow.com/questions/18870447/how-to-use-the-default-ios7-uianimation-curve - return (UIViewAnimationOptions)(_currentKeyboardAnimationCurve << 16); - default: - RCTLogError(@"Unsupported animation type %zd", type); - return UIViewAnimationOptionCurveEaseInOut; - } -} - -// Use a custom initialization function rather than implementing `+initialize` so that we can control -// when the initialization code runs. `+initialize` runs immediately before the first message is sent -// to the class which may be too late for us. By this time, we may have missed some -// `UIKeyboardWillChangeFrameNotification`s. -+ (void)initializeStatics -{ -#if !TARGET_OS_TV - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(keyboardWillChangeFrame:) - name:UIKeyboardWillChangeFrameNotification - object:nil]; - }); -#endif -} - -+ (void)keyboardWillChangeFrame:(NSNotification *)notification -{ -#if !TARGET_OS_TV - NSDictionary *userInfo = notification.userInfo; - _currentKeyboardAnimationCurve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]; -#endif -} - -- (instancetype)initWithDuration:(NSTimeInterval)duration dictionary:(NSDictionary *)config -{ - if (!config) { - return nil; - } - - if ((self = [super init])) { - _property = [RCTConvert NSString:config[@"property"]]; - - _duration = [RCTConvert NSTimeInterval:config[@"duration"]] ?: duration; - if (_duration > 0.0 && _duration < 0.01) { - RCTLogError(@"RCTLayoutAnimation expects timings to be in ms, not seconds."); - _duration = _duration * 1000.0; - } - - _delay = [RCTConvert NSTimeInterval:config[@"delay"]]; - if (_delay > 0.0 && _delay < 0.01) { - RCTLogError(@"RCTLayoutAnimation expects timings to be in ms, not seconds."); - _delay = _delay * 1000.0; - } - - _animationType = [RCTConvert RCTAnimationType:config[@"type"]]; - if (_animationType == RCTAnimationTypeSpring) { - _springDamping = [RCTConvert CGFloat:config[@"springDamping"]]; - _initialVelocity = [RCTConvert CGFloat:config[@"initialVelocity"]]; - } - } - return self; -} - -- (void)performAnimations:(void (^)(void))animations - withCompletionBlock:(void (^)(BOOL completed))completionBlock -{ - if (_animationType == RCTAnimationTypeSpring) { - - [UIView animateWithDuration:_duration - delay:_delay - usingSpringWithDamping:_springDamping - initialSpringVelocity:_initialVelocity - options:UIViewAnimationOptionBeginFromCurrentState - animations:animations - completion:completionBlock]; - - } else { - - UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState | - UIViewAnimationOptionsFromRCTAnimationType(_animationType); - - [UIView animateWithDuration:_duration - delay:_delay - options:options - animations:animations - completion:completionBlock]; - } -} - -@end - -@interface RCTLayoutAnimation : NSObject - -@property (nonatomic, copy) NSDictionary *config; -@property (nonatomic, strong) RCTAnimation *createAnimation; -@property (nonatomic, strong) RCTAnimation *updateAnimation; -@property (nonatomic, strong) RCTAnimation *deleteAnimation; -@property (nonatomic, copy) RCTResponseSenderBlock callback; - -@end - -@implementation RCTLayoutAnimation - -- (instancetype)initWithDictionary:(NSDictionary *)config callback:(RCTResponseSenderBlock)callback -{ - if (!config) { - return nil; - } - - if ((self = [super init])) { - _config = [config copy]; - NSTimeInterval duration = [RCTConvert NSTimeInterval:config[@"duration"]]; - if (duration > 0.0 && duration < 0.01) { - RCTLogError(@"RCTLayoutAnimation expects timings to be in ms, not seconds."); - duration = duration * 1000.0; - } - - _createAnimation = [[RCTAnimation alloc] initWithDuration:duration dictionary:config[@"create"]]; - _updateAnimation = [[RCTAnimation alloc] initWithDuration:duration dictionary:config[@"update"]]; - _deleteAnimation = [[RCTAnimation alloc] initWithDuration:duration dictionary:config[@"delete"]]; - _callback = callback; - } - return self; -} - -@end - @implementation RCTUIManager { // Root views are only mutated on the shadow queue @@ -216,7 +64,7 @@ static UIViewAnimationOptions UIViewAnimationOptionsFromRCTAnimationType(RCTAnim NSMutableArray *_pendingUIBlocks; // Animation - RCTLayoutAnimation *_layoutAnimation; // Main thread only + RCTLayoutAnimationGroup *_layoutAnimationGroup; // Main thread only NSMutableSet *_viewsToBeDeleted; // Main thread only NSMutableDictionary *_shadowViewRegistry; // RCT thread only @@ -326,7 +174,7 @@ RCT_EXPORT_MODULE() selector:@selector(didReceiveNewContentSizeMultiplier) name:RCTAccessibilityManagerDidUpdateMultiplierNotification object:_bridge.accessibilityManager]; - [RCTAnimation initializeStatics]; + [RCTLayoutAnimation initializeStatics]; } dispatch_queue_t RCTGetUIManagerQueue(void) @@ -559,6 +407,19 @@ BOOL RCTIsUIManagerQueue() [_pendingUIBlocks insertObject:block atIndex:0]; } +- (void)setNextLayoutAnimationGroup:(RCTLayoutAnimationGroup *)layoutAnimationGroup +{ + RCTAssertMainQueue(); + + if (_layoutAnimationGroup && ![_layoutAnimationGroup isEqual:layoutAnimationGroup]) { + RCTLogWarn(@"Warning: Overriding previous layout animation with new one before the first began:\n%@ -> %@.", + [_layoutAnimationGroup description], + [layoutAnimationGroup description]); + } + + _layoutAnimationGroup = layoutAnimationGroup; +} + - (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView *)rootShadowView { RCTAssert(!RCTIsMainQueue(), @"Should be called on shadow queue"); @@ -649,7 +510,7 @@ BOOL RCTIsUIManagerQueue() return ^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { const RCTFrameData *frameDataArray = (const RCTFrameData *)framesData.bytes; - RCTLayoutAnimation *layoutAnimation = uiManager->_layoutAnimation; + RCTLayoutAnimationGroup *layoutAnimationGroup = uiManager->_layoutAnimationGroup; __block NSUInteger completionsCalled = 0; @@ -663,18 +524,18 @@ BOOL RCTIsUIManagerQueue() BOOL isHidden = frameData.isHidden; UIUserInterfaceLayoutDirection layoutDirection = frameData.layoutDirection; BOOL isNew = frameData.isNew; - RCTAnimation *updateAnimation = isNew ? nil : layoutAnimation.updateAnimation; + RCTLayoutAnimation *updatingLayoutAnimation = isNew ? nil : layoutAnimationGroup.updatingLayoutAnimation; BOOL shouldAnimateCreation = isNew && !frameData.parentIsNew; - RCTAnimation *createAnimation = shouldAnimateCreation ? layoutAnimation.createAnimation : nil; + RCTLayoutAnimation *creatingLayoutAnimation = shouldAnimateCreation ? layoutAnimationGroup.creatingLayoutAnimation : nil; void (^completion)(BOOL) = ^(BOOL finished) { completionsCalled++; - if (layoutAnimation.callback && completionsCalled == count) { - layoutAnimation.callback(@[@(finished)]); + if (layoutAnimationGroup.callback && completionsCalled == count) { + layoutAnimationGroup.callback(@[@(finished)]); // It's unsafe to call this callback more than once, so we nil it out here // to make sure that doesn't happen. - layoutAnimation.callback = nil; + layoutAnimationGroup.callback = nil; } }; @@ -687,7 +548,7 @@ BOOL RCTIsUIManagerQueue() } RCTViewManagerUIBlock updateBlock = updateBlocks[reactTag]; - if (createAnimation) { + if (creatingLayoutAnimation) { // Animate view creation [view reactSetFrame:frame]; @@ -695,17 +556,17 @@ BOOL RCTIsUIManagerQueue() CATransform3D finalTransform = view.layer.transform; CGFloat finalOpacity = view.layer.opacity; - NSString *property = createAnimation.property; + NSString *property = creatingLayoutAnimation.property; if ([property isEqualToString:@"scaleXY"]) { view.layer.transform = CATransform3DMakeScale(0, 0, 0); } else if ([property isEqualToString:@"opacity"]) { view.layer.opacity = 0.0; } else { RCTLogError(@"Unsupported layout animation createConfig property %@", - createAnimation.property); + creatingLayoutAnimation.property); } - [createAnimation performAnimations:^{ + [creatingLayoutAnimation performAnimations:^{ if ([property isEqualToString:@"scaleXY"]) { view.layer.transform = finalTransform; } else if ([property isEqualToString:@"opacity"]) { @@ -716,10 +577,10 @@ BOOL RCTIsUIManagerQueue() } } withCompletionBlock:completion]; - } else if (updateAnimation) { + } else if (updatingLayoutAnimation) { // Animate view update - [updateAnimation performAnimations:^{ + [updatingLayoutAnimation performAnimations:^{ [view reactSetFrame:frame]; if (updateBlock) { updateBlock(self, viewRegistry); @@ -738,7 +599,7 @@ BOOL RCTIsUIManagerQueue() } // Clean up - uiManager->_layoutAnimation = nil; + uiManager->_layoutAnimationGroup = nil; }; } @@ -823,10 +684,10 @@ RCT_EXPORT_METHOD(removeSubviewsFromContainerWithID:(nonnull NSNumber *)containe */ - (void)_removeChildren:(NSArray *)children fromContainer:(UIView *)container - withAnimation:(RCTLayoutAnimation *)animation + withAnimation:(RCTLayoutAnimationGroup *)animation { RCTAssertMainQueue(); - RCTAnimation *deleteAnimation = animation.deleteAnimation; + RCTLayoutAnimation *deletingLayoutAnimation = animation.deletingLayoutAnimation; __block NSUInteger completionsCalled = 0; for (UIView *removedChild in children) { @@ -852,15 +713,15 @@ RCT_EXPORT_METHOD(removeSubviewsFromContainerWithID:(nonnull NSNumber *)containe // the view events anyway. removedChild.userInteractionEnabled = NO; - NSString *property = deleteAnimation.property; - [deleteAnimation performAnimations:^{ + NSString *property = deletingLayoutAnimation.property; + [deletingLayoutAnimation performAnimations:^{ if ([property isEqualToString:@"scaleXY"]) { removedChild.layer.transform = CATransform3DMakeScale(0.001, 0.001, 0.001); } else if ([property isEqualToString:@"opacity"]) { removedChild.layer.opacity = 0.0; } else { RCTLogError(@"Unsupported layout animation createConfig property %@", - deleteAnimation.property); + deletingLayoutAnimation.property); } } withCompletionBlock:completion]; } @@ -985,10 +846,10 @@ RCT_EXPORT_METHOD(manageChildren:(nonnull NSNumber *)containerTag [self _childrenToRemoveFromContainer:container atIndices:moveFromIndices]; BOOL isUIViewRegistry = ((id)registry == (id)_viewRegistry); - if (isUIViewRegistry && _layoutAnimation.deleteAnimation) { + if (isUIViewRegistry && _layoutAnimationGroup.deletingLayoutAnimation) { [self _removeChildren:(NSArray *)permanentlyRemovedChildren fromContainer:(UIView *)container - withAnimation:_layoutAnimation]; + withAnimation:_layoutAnimationGroup]; } else { [self _removeChildren:permanentlyRemovedChildren fromContainer:container]; } @@ -1587,18 +1448,12 @@ RCT_EXPORT_METHOD(configureNextLayoutAnimation:(NSDictionary *)config withCallback:(RCTResponseSenderBlock)callback errorCallback:(__unused RCTResponseSenderBlock)errorCallback) { - RCTLayoutAnimation *currentAnimation = _layoutAnimation; + RCTLayoutAnimationGroup *layoutAnimationGroup = + [[RCTLayoutAnimationGroup alloc] initWithConfig:config + callback:callback]; - if (currentAnimation && ![config isEqualToDictionary:currentAnimation.config]) { - RCTLogWarn(@"Warning: Overriding previous layout animation with new one before the first began:\n%@ -> %@.", currentAnimation.config, config); - } - - RCTLayoutAnimation *nextLayoutAnimation = [[RCTLayoutAnimation alloc] initWithDictionary:config - callback:callback]; - - // Set up next layout animation [self addUIBlock:^(RCTUIManager *uiManager, __unused NSDictionary *viewRegistry) { - uiManager->_layoutAnimation = nextLayoutAnimation; + [uiManager setNextLayoutAnimationGroup:layoutAnimationGroup]; }]; } diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index 6e3d0a11d..6bee292ac 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -999,6 +999,18 @@ 590D7BFE1EBD458B00D8A370 /* RCTShadowView+Layout.h in Headers */ = {isa = PBXBuildFile; fileRef = 590D7BFB1EBD458B00D8A370 /* RCTShadowView+Layout.h */; }; 590D7BFF1EBD458B00D8A370 /* RCTShadowView+Layout.m in Sources */ = {isa = PBXBuildFile; fileRef = 590D7BFC1EBD458B00D8A370 /* RCTShadowView+Layout.m */; }; 590D7C001EBD458B00D8A370 /* RCTShadowView+Layout.m in Sources */ = {isa = PBXBuildFile; fileRef = 590D7BFC1EBD458B00D8A370 /* RCTShadowView+Layout.m */; }; + 5960C1B51F0804A00066FD5B /* RCTLayoutAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = 5960C1B11F0804A00066FD5B /* RCTLayoutAnimation.h */; }; + 5960C1B61F0804A00066FD5B /* RCTLayoutAnimation.h in Headers */ = {isa = PBXBuildFile; fileRef = 5960C1B11F0804A00066FD5B /* RCTLayoutAnimation.h */; }; + 5960C1B71F0804A00066FD5B /* RCTLayoutAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 5960C1B21F0804A00066FD5B /* RCTLayoutAnimation.m */; }; + 5960C1B81F0804A00066FD5B /* RCTLayoutAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 5960C1B21F0804A00066FD5B /* RCTLayoutAnimation.m */; }; + 5960C1B91F0804A00066FD5B /* RCTLayoutAnimationGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 5960C1B31F0804A00066FD5B /* RCTLayoutAnimationGroup.h */; }; + 5960C1BA1F0804A00066FD5B /* RCTLayoutAnimationGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 5960C1B31F0804A00066FD5B /* RCTLayoutAnimationGroup.h */; }; + 5960C1BB1F0804A00066FD5B /* RCTLayoutAnimationGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 5960C1B41F0804A00066FD5B /* RCTLayoutAnimationGroup.m */; }; + 5960C1BC1F0804A00066FD5B /* RCTLayoutAnimationGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 5960C1B41F0804A00066FD5B /* RCTLayoutAnimationGroup.m */; }; + 5960C1BD1F0804DF0066FD5B /* RCTLayoutAnimation.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5960C1B11F0804A00066FD5B /* RCTLayoutAnimation.h */; }; + 5960C1BE1F0804DF0066FD5B /* RCTLayoutAnimationGroup.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5960C1B31F0804A00066FD5B /* RCTLayoutAnimationGroup.h */; }; + 5960C1BF1F0804F50066FD5B /* RCTLayoutAnimation.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5960C1B11F0804A00066FD5B /* RCTLayoutAnimation.h */; }; + 5960C1C01F0804F50066FD5B /* RCTLayoutAnimationGroup.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 5960C1B31F0804A00066FD5B /* RCTLayoutAnimationGroup.h */; }; 59A7B9FD1E577DBF0068EDBF /* RCTRootContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = 59A7B9FB1E577DBF0068EDBF /* RCTRootContentView.h */; }; 59A7B9FE1E577DBF0068EDBF /* RCTRootContentView.m in Sources */ = {isa = PBXBuildFile; fileRef = 59A7B9FC1E577DBF0068EDBF /* RCTRootContentView.m */; }; 59B1EBC91EBD46250047B19B /* RCTShadowView+Layout.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 590D7BFB1EBD458B00D8A370 /* RCTShadowView+Layout.h */; }; @@ -1169,6 +1181,8 @@ dstPath = include/React; dstSubfolderSpec = 16; files = ( + 5960C1BF1F0804F50066FD5B /* RCTLayoutAnimation.h in Copy Headers */, + 5960C1C01F0804F50066FD5B /* RCTLayoutAnimationGroup.h in Copy Headers */, C6827DFC1EF1801B00D66BEF /* RCTJSEnvironment.h in Copy Headers */, 59EB6DC01EBD70130072A5E7 /* RCTUIManagerObserverCoordinator.h in Copy Headers */, 59B1EBCA1EBD47520047B19B /* RCTShadowView+Layout.h in Copy Headers */, @@ -1389,6 +1403,8 @@ dstPath = include/React; dstSubfolderSpec = 16; files = ( + 5960C1BD1F0804DF0066FD5B /* RCTLayoutAnimation.h in Copy Headers */, + 5960C1BE1F0804DF0066FD5B /* RCTLayoutAnimationGroup.h in Copy Headers */, C6827DFB1EF1800E00D66BEF /* RCTJSEnvironment.h in Copy Headers */, 59EB6DBF1EBD6FFC0072A5E7 /* RCTUIManagerObserverCoordinator.h in Copy Headers */, 59B1EBC91EBD46250047B19B /* RCTShadowView+Layout.h in Copy Headers */, @@ -1921,6 +1937,10 @@ 58C571C01AA56C1900CDF9C8 /* RCTDatePickerManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTDatePickerManager.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 590D7BFB1EBD458B00D8A370 /* RCTShadowView+Layout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTShadowView+Layout.h"; sourceTree = ""; }; 590D7BFC1EBD458B00D8A370 /* RCTShadowView+Layout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTShadowView+Layout.m"; sourceTree = ""; }; + 5960C1B11F0804A00066FD5B /* RCTLayoutAnimation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTLayoutAnimation.h; sourceTree = ""; }; + 5960C1B21F0804A00066FD5B /* RCTLayoutAnimation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTLayoutAnimation.m; sourceTree = ""; }; + 5960C1B31F0804A00066FD5B /* RCTLayoutAnimationGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTLayoutAnimationGroup.h; sourceTree = ""; }; + 5960C1B41F0804A00066FD5B /* RCTLayoutAnimationGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTLayoutAnimationGroup.m; sourceTree = ""; }; 59A7B9FB1E577DBF0068EDBF /* RCTRootContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRootContentView.h; sourceTree = ""; }; 59A7B9FC1E577DBF0068EDBF /* RCTRootContentView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRootContentView.m; sourceTree = ""; }; 59EB6DB91EBD6FC90072A5E7 /* RCTUIManagerObserverCoordinator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTUIManagerObserverCoordinator.h; sourceTree = ""; }; @@ -2185,6 +2205,10 @@ 369123E01DDC75850095B341 /* RCTJSCSamplingProfiler.m */, 13D9FEEC1CDCD93000158BD7 /* RCTKeyboardObserver.h */, 13D9FEED1CDCD93000158BD7 /* RCTKeyboardObserver.m */, + 5960C1B11F0804A00066FD5B /* RCTLayoutAnimation.h */, + 5960C1B21F0804A00066FD5B /* RCTLayoutAnimation.m */, + 5960C1B31F0804A00066FD5B /* RCTLayoutAnimationGroup.h */, + 5960C1B41F0804A00066FD5B /* RCTLayoutAnimationGroup.m */, 13F17A831B8493E5007D4C75 /* RCTRedBox.h */, 13F17A841B8493E5007D4C75 /* RCTRedBox.m */, 000E6CE91AB0E97F000CDF4D /* RCTSourceCode.h */, @@ -2503,8 +2527,8 @@ 657734881EE8352500A0E9EA /* Inspector */, 13B07FE01A69315300A75B9A /* Modules */, 1450FF7F1BCFF28A00208362 /* Profiler */, - 13B07FF31A6947C200A75B9A /* Views */, 3D788F841EBD2D240063D616 /* third-party.xcconfig */, + 13B07FF31A6947C200A75B9A /* Views */, ); name = React; sourceTree = ""; @@ -2668,6 +2692,7 @@ 13134C8D1E296B2A00B9F3CB /* RCTMessageThread.h in Headers */, 130443DE1E401B0D00D93A67 /* RCTTVView.h in Headers */, 3D7AA9C51E548CDB001955CF /* NSDataBigString.h in Headers */, + 5960C1BA1F0804A00066FD5B /* RCTLayoutAnimationGroup.h in Headers */, 13134C991E296B2A00B9F3CB /* RCTCxxMethod.h in Headers */, 3D302F471DF828F800D6DDAE /* RCTPlatform.h in Headers */, 13134C951E296B2A00B9F3CB /* RCTObjcExecutor.h in Headers */, @@ -2713,6 +2738,7 @@ 3D7BFD261EA8E351008DFB7A /* RCTSamplingProfilerPackagerMethod.h in Headers */, 3D302F3F1DF828F800D6DDAE /* RCTLog.h in Headers */, 3D302F401DF828F800D6DDAE /* RCTModuleData.h in Headers */, + 5960C1B61F0804A00066FD5B /* RCTLayoutAnimation.h in Headers */, 3D302F411DF828F800D6DDAE /* RCTModuleMethod.h in Headers */, 3D302F421DF828F800D6DDAE /* RCTMultipartDataTask.h in Headers */, 3D302F431DF828F800D6DDAE /* RCTMultipartStreamReader.h in Headers */, @@ -2970,6 +2996,7 @@ 3D7BFD151EA8E351008DFB7A /* RCTPackagerClient.h in Headers */, 3D80DA251DF820620028D040 /* RCTBridgeModule.h in Headers */, 3D80DA261DF820620028D040 /* RCTBundleURLProvider.h in Headers */, + 5960C1B51F0804A00066FD5B /* RCTLayoutAnimation.h in Headers */, 3D80DA271DF820620028D040 /* RCTConvert.h in Headers */, 3D80DA281DF820620028D040 /* RCTDefines.h in Headers */, 3D80DA291DF820620028D040 /* RCTDisplayLink.h in Headers */, @@ -3037,6 +3064,7 @@ 3D80DA5E1DF820620028D040 /* RCTProfile.h in Headers */, 3D80DA5F1DF820620028D040 /* RCTActivityIndicatorView.h in Headers */, 3D80DA601DF820620028D040 /* RCTActivityIndicatorViewManager.h in Headers */, + 5960C1B91F0804A00066FD5B /* RCTLayoutAnimationGroup.h in Headers */, C6194AB01EF156280034D062 /* RCTPackagerConnectionConfig.h in Headers */, CF2731C01E7B8DE40044CA4F /* RCTDeviceInfo.h in Headers */, 3D80DA611DF820620028D040 /* RCTAnimationType.h in Headers */, @@ -3532,6 +3560,7 @@ 2D3B5EA31D9B08BE00451313 /* RCTParserUtils.m in Sources */, 2D3B5EA01D9B08B200451313 /* RCTLog.mm in Sources */, 2D3B5EE21D9B09B400451313 /* RCTScrollViewManager.m in Sources */, + 5960C1BC1F0804A00066FD5B /* RCTLayoutAnimationGroup.m in Sources */, 2D3B5ECF1D9B096F00451313 /* RCTFont.mm in Sources */, 2D3B5ED51D9B098000451313 /* RCTModalHostViewController.m in Sources */, 2D3B5EBC1D9B092600451313 /* RCTKeyboardObserver.m in Sources */, @@ -3545,6 +3574,7 @@ 2D3B5EE31D9B09B700451313 /* RCTSegmentedControl.m in Sources */, 130443A41E3FEAC600D93A67 /* RCTFollyConvert.mm in Sources */, 3D7BFD201EA8E351008DFB7A /* RCTPackagerConnection.m in Sources */, + 5960C1B81F0804A00066FD5B /* RCTLayoutAnimation.m in Sources */, 2D3B5EB71D9B091800451313 /* RCTRedBox.m in Sources */, 3D7AA9C61E548CDD001955CF /* NSDataBigString.mm in Sources */, 13134C8F1E296B2A00B9F3CB /* RCTMessageThread.mm in Sources */, @@ -3811,6 +3841,7 @@ 13D9FEEB1CDCCECF00158BD7 /* RCTEventEmitter.m in Sources */, AC70D2E91DE489E4002E6351 /* RCTJavaScriptLoader.mm in Sources */, 14F7A0EC1BDA3B3C003C6C10 /* RCTPerfMonitor.m in Sources */, + 5960C1B71F0804A00066FD5B /* RCTLayoutAnimation.m in Sources */, 13134C9E1E296B2A00B9F3CB /* RCTCxxModule.mm in Sources */, 1450FF881BCFF28A00208362 /* RCTProfileTrampoline-arm64.S in Sources */, 13E41EEB1C05CA0B00CD8DAC /* RCTProfileTrampoline-i386.S in Sources */, @@ -3868,6 +3899,7 @@ 68EFE4EE1CF6EB3900A1DE13 /* RCTBundleURLProvider.m in Sources */, B95154321D1B34B200FE7B80 /* RCTActivityIndicatorView.m in Sources */, 59FBEFB21E46D91C0095D885 /* RCTScrollContentShadowView.m in Sources */, + 5960C1BB1F0804A00066FD5B /* RCTLayoutAnimationGroup.m in Sources */, 13B0801A1A69489C00A75B9A /* RCTNavigator.m in Sources */, 137327E71AA5CF210034F82E /* RCTTabBar.m in Sources */, 13F17A851B8493E5007D4C75 /* RCTRedBox.m in Sources */,