feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,12 @@
#ifdef RCT_NEW_ARCH_ENABLED
#import <RNReanimated/REAModule.h>
#import <React/RCTFabricSurface.h>
@interface REAInitializerRCTFabricSurface : RCTFabricSurface
@property REAModule *reaModule;
@end
#endif // RCT_NEW_ARCH_ENABLED

View File

@@ -0,0 +1,74 @@
#ifdef RCT_NEW_ARCH_ENABLED
#import <React/RCTSurface.h>
#import <React/RCTSurfaceView.h>
#import <memory>
#import <RNReanimated/REAInitializerRCTFabricSurface.h>
#import <RNReanimated/REAModule.h>
@implementation REAInitializerRCTFabricSurface {
std::shared_ptr<facebook::react::SurfaceHandler> _surfaceHandler;
int _tag;
RCTSurface *_surface;
}
- (instancetype)init
{
if (self = [super init]) {
_tag = -1;
_surface = [[RCTSurface alloc] init];
_surfaceHandler = std::make_shared<facebook::react::SurfaceHandler>("REASurface", _tag);
}
return self;
}
- (NSNumber *)rootViewTag
{
return @(_tag);
}
- (NSInteger)rootTag
{
return (NSInteger)_tag;
}
- (void)start
{
// this is only needed method, the rest of them is just for prevent null pointer exceptions
[_reaModule installReanimatedAfterReload];
}
- (facebook::react::SurfaceHandler const &)surfaceHandler
{
return *_surfaceHandler.get();
}
- (void)setMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize
{
}
- (void)setMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize viewportOffset:(CGPoint)viewportOffset
{
}
- (void)stop
{
}
- (CGSize)sizeThatFitsMinimumSize:(CGSize)minimumSize maximumSize:(CGSize)maximumSize
{
CGSize size{0, 0};
return size;
}
- (nonnull RCTSurfaceView *)view
{
// This method should never be called.
react_native_assert(false);
return nullptr;
}
@end
#endif // RCT_NEW_ARCH_ENABLED

View File

@@ -0,0 +1,72 @@
#import <Foundation/Foundation.h>
#import <RNReanimated/LayoutAnimationType.h>
#import <RNReanimated/REANodesManager.h>
#import <RNReanimated/REASnapshot.h>
#import <RNReanimated/REAUIKit.h>
#import <React/RCTUIManager.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, ViewState) {
Inactive,
Appearing,
Disappearing,
Layout,
ToRemove,
};
typedef BOOL (^REAHasAnimationBlock)(NSNumber *_Nonnull tag, LayoutAnimationType type);
typedef BOOL (^REAShouldAnimateExitingBlock)(NSNumber *_Nonnull tag, BOOL shouldAnimate);
typedef void (
^REAAnimationStartingBlock)(NSNumber *_Nonnull tag, LayoutAnimationType type, NSDictionary *_Nonnull yogaValues);
typedef void (^REAAnimationRemovingBlock)(NSNumber *_Nonnull tag);
typedef void (^REASharedTransitionRemovingBlock)(NSNumber *_Nonnull tag);
#ifndef NDEBUG
typedef void (^REACheckDuplicateSharedTagBlock)(REAUIView *view, NSNumber *_Nonnull viewTag);
#endif
typedef void (^REACancelAnimationBlock)(NSNumber *_Nonnull tag);
typedef NSNumber *_Nullable (^REAFindPrecedingViewTagForTransitionBlock)(NSNumber *_Nonnull tag);
typedef int (^REATreeVisitor)(id<RCTComponent>);
BOOL REANodeFind(id<RCTComponent> view, int (^block)(id<RCTComponent>));
@interface REAAnimationsManager : NSObject
- (instancetype)initWithUIManager:(RCTUIManager *)uiManager;
- (void)setAnimationStartingBlock:(REAAnimationStartingBlock)startAnimation;
- (void)setHasAnimationBlock:(REAHasAnimationBlock)hasAnimation;
- (void)setShouldAnimateExitingBlock:(REAShouldAnimateExitingBlock)shouldAnimateExiting;
- (void)setAnimationRemovingBlock:(REAAnimationRemovingBlock)clearAnimation;
- (void)setSharedTransitionRemovingBlock:(REASharedTransitionRemovingBlock)clearSharedTransition;
#ifndef NDEBUG
- (void)setCheckDuplicateSharedTagBlock:(REACheckDuplicateSharedTagBlock)checkDuplicateSharedTag;
#endif
- (void)progressLayoutAnimationWithStyle:(NSDictionary *_Nonnull)newStyle
forTag:(NSNumber *_Nonnull)tag
isSharedTransition:(BOOL)isSharedTransition;
- (void)setFindPrecedingViewTagForTransitionBlock:
(REAFindPrecedingViewTagForTransitionBlock)findPrecedingViewTagForTransition;
- (void)setCancelAnimationBlock:(REACancelAnimationBlock)animationCancellingBlock;
- (void)endLayoutAnimationForTag:(NSNumber *_Nonnull)tag removeView:(BOOL)removeView;
- (void)endAnimationsRecursive:(REAUIView *)view;
- (void)invalidate;
- (void)viewDidMount:(REAUIView *)view withBeforeSnapshot:(REASnapshot *)snapshot withNewFrame:(CGRect)frame;
- (REASnapshot *)prepareSnapshotBeforeMountForView:(REAUIView *)view;
- (void)removeAnimationsFromSubtree:(REAUIView *)view;
- (void)reattachAnimatedChildren:(NSArray<id<RCTComponent>> *)children
toContainer:(id<RCTComponent>)container
atIndices:(NSArray<NSNumber *> *)indices;
- (void)onViewCreate:(REAUIView *)view after:(REASnapshot *)after;
- (void)onViewUpdate:(REAUIView *)view before:(REASnapshot *)before after:(REASnapshot *)after;
- (void)viewsDidLayout;
- (NSMutableDictionary *)prepareDataForLayoutAnimatingWorklet:(NSMutableDictionary *)currentValues
targetValues:(NSMutableDictionary *)targetValues;
- (REAUIView *)viewForTag:(NSNumber *)tag;
- (BOOL)hasAnimationForTag:(NSNumber *)tag type:(LayoutAnimationType)type;
- (void)clearAnimationConfigForTag:(NSNumber *)tag;
- (void)clearSharedTransitionConfigForTag:(NSNumber *)tag;
- (void)startAnimationForTag:(NSNumber *)tag type:(LayoutAnimationType)type yogaValues:(NSDictionary *)yogaValues;
- (void)onScreenRemoval:(REAUIView *)screen stack:(REAUIView *)stack;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,638 @@
#import <RNReanimated/REAAnimationsManager.h>
#import <RNReanimated/REASharedElement.h>
#import <RNReanimated/REASharedTransitionManager.h>
#import <RNReanimated/REASwizzledUIManager.h>
#import <React/RCTComponentData.h>
#import <React/RCTTextView.h>
#import <React/UIView+Private.h>
#import <React/UIView+React.h>
typedef NS_ENUM(NSInteger, FrameConfigType) { EnteringFrame, ExitingFrame };
BOOL REANodeFind(id<RCTComponent> view, int (^block)(id<RCTComponent>))
{
if (!view.reactTag) {
return NO;
}
if (block(view)) {
return YES;
}
for (id<RCTComponent> subview in view.reactSubviews) {
if (REANodeFind(subview, block)) {
return YES;
}
}
return NO;
}
@implementation REAAnimationsManager {
RCTUIManager *_uiManager;
REASwizzledUIManager *_reaSwizzledUIManager;
NSMutableSet<NSNumber *> *_enteringViews;
NSMutableDictionary<NSNumber *, REASnapshot *> *_enteringViewTargetValues;
NSMutableDictionary<NSNumber *, REAUIView *> *_exitingViews;
NSMutableDictionary<NSNumber *, NSNumber *> *_exitingSubviewsCountMap;
NSMutableDictionary<NSNumber *, NSNumber *> *_exitingParentTags;
NSMutableSet<NSNumber *> *_ancestorsToRemove;
NSMutableArray<NSString *> *_targetKeys;
NSMutableArray<NSString *> *_currentKeys;
REAAnimationStartingBlock _startAnimationForTag;
REAHasAnimationBlock _hasAnimationForTag;
REAShouldAnimateExitingBlock _shouldAnimateExiting;
REAAnimationRemovingBlock _clearAnimationConfigForTag;
REASharedTransitionRemovingBlock _clearSharedTransitionConfigForTag;
REASharedTransitionManager *_sharedTransitionManager;
#ifndef NDEBUG
REACheckDuplicateSharedTagBlock _checkDuplicateSharedTag;
#endif
}
+ (NSArray *)layoutKeys
{
static NSArray *_array;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_array = @[ @"originX", @"originY", @"width", @"height" ];
});
return _array;
}
- (instancetype)initWithUIManager:(RCTUIManager *)uiManager
{
if (self = [super init]) {
_uiManager = uiManager;
_exitingViews = [NSMutableDictionary new];
_exitingSubviewsCountMap = [NSMutableDictionary new];
_ancestorsToRemove = [NSMutableSet new];
_exitingParentTags = [NSMutableDictionary new];
_enteringViews = [NSMutableSet new];
_enteringViewTargetValues = [NSMutableDictionary new];
_targetKeys = [NSMutableArray new];
_currentKeys = [NSMutableArray new];
for (NSString *key in [[self class] layoutKeys]) {
[_targetKeys addObject:[NSString stringWithFormat:@"target%@", [key capitalizedString]]];
[_currentKeys addObject:[NSString stringWithFormat:@"current%@", [key capitalizedString]]];
}
_sharedTransitionManager = [[REASharedTransitionManager alloc] initWithAnimationsManager:self];
_reaSwizzledUIManager = [[REASwizzledUIManager alloc] initWithUIManager:uiManager withAnimationManager:self];
_startAnimationForTag = ^(NSNumber *tag, LayoutAnimationType type, NSDictionary *yogaValues) {
// default implementation, this block will be replaced by a setter
};
_hasAnimationForTag = ^(NSNumber *tag, LayoutAnimationType type) {
// default implementation, this block will be replaced by a setter
return NO;
};
_shouldAnimateExiting = ^(NSNumber *tag, BOOL shouldAnimate) {
// default implementation, this block will be replaced by a setter
return YES;
};
_clearAnimationConfigForTag = ^(NSNumber *tag) {
// default implementation, this block will be replaced by a setter
};
_clearSharedTransitionConfigForTag = ^(NSNumber *tag) {
// default implementation, this block will be replaced by a setter
};
#ifndef NDEBUG
_checkDuplicateSharedTag = ^(REAUIView *view, NSNumber *viewTag) {
// default implementation, this block will be replaced by a setter
};
#endif
}
return self;
}
- (void)invalidate
{
_startAnimationForTag = nil;
_hasAnimationForTag = nil;
_uiManager = nil;
_exitingViews = nil;
_targetKeys = nil;
_currentKeys = nil;
}
- (void)setAnimationStartingBlock:(REAAnimationStartingBlock)startAnimation
{
_startAnimationForTag = startAnimation;
}
- (void)setHasAnimationBlock:(REAHasAnimationBlock)hasAnimation
{
_hasAnimationForTag = hasAnimation;
}
- (void)setShouldAnimateExitingBlock:(REAShouldAnimateExitingBlock)shouldAnimateExiting
{
_shouldAnimateExiting = shouldAnimateExiting;
}
- (void)setAnimationRemovingBlock:(REAAnimationRemovingBlock)clearAnimation
{
_clearAnimationConfigForTag = clearAnimation;
}
- (void)setSharedTransitionRemovingBlock:(REASharedTransitionRemovingBlock)clearSharedTransition
{
_clearSharedTransitionConfigForTag = clearSharedTransition;
}
#ifndef NDEBUG
- (void)setCheckDuplicateSharedTagBlock:(REACheckDuplicateSharedTagBlock)checkDuplicateSharedTag
{
_checkDuplicateSharedTag = checkDuplicateSharedTag;
}
#endif
- (REAUIView *)viewForTag:(NSNumber *)tag
{
REAUIView *view;
(view = [_uiManager viewForReactTag:tag]) || (view = [_exitingViews objectForKey:tag]) ||
(view = [_sharedTransitionManager getTransitioningView:tag]);
return view;
}
- (void)endLayoutAnimationForTag:(NSNumber *)tag removeView:(BOOL)removeView
{
REAUIView *view = [self viewForTag:tag];
if (view == nil) {
return;
}
if ([_enteringViews containsObject:tag] && !removeView) {
REASnapshot *target = _enteringViewTargetValues[tag];
if (target != nil) {
[self setNewProps:target.values forView:view];
}
}
[_enteringViews removeObject:tag];
[_enteringViewTargetValues removeObjectForKey:tag];
if (removeView) {
[self endAnimationsRecursive:view];
[view removeFromSuperview];
}
[_sharedTransitionManager finishSharedAnimation:[self viewForTag:tag] removeView:removeView];
}
- (void)endAnimationsRecursive:(REAUIView *)view
{
NSNumber *tag = [view reactTag];
if (tag == nil) {
return;
}
// we'll remove this view anyway when exiting from recursion,
// no need to remove it in `maybeDropAncestors`
[_ancestorsToRemove removeObject:tag];
for (REAUIView *child in [[view subviews] copy]) {
[self endAnimationsRecursive:child];
}
if ([_exitingViews objectForKey:tag]) {
[_exitingViews removeObjectForKey:tag];
[self maybeDropAncestors:view];
}
}
- (void)progressLayoutAnimationWithStyle:(NSDictionary *)newStyle
forTag:(NSNumber *)tag
isSharedTransition:(BOOL)isSharedTransition
{
[self setNewProps:[newStyle mutableCopy] forView:[self viewForTag:tag] convertFromAbsolute:isSharedTransition];
}
- (double)getDoubleOrZero:(NSNumber *)number
{
double doubleValue = [number doubleValue];
if (doubleValue != doubleValue) { // NaN != NaN
return 0;
}
return doubleValue;
}
- (void)setNewProps:(NSMutableDictionary *)newProps forView:(REAUIView *)view
{
[self setNewProps:newProps forView:view convertFromAbsolute:NO];
}
- (void)setNewProps:(NSMutableDictionary *)newProps
forView:(REAUIView *)view
convertFromAbsolute:(BOOL)convertFromAbsolute
{
if (newProps[@"height"]) {
double height = [self getDoubleOrZero:newProps[@"height"]];
double oldHeight = view.bounds.size.height;
view.bounds = CGRectMake(0, 0, view.bounds.size.width, height);
view.center = CGPointMake(view.center.x, view.center.y - oldHeight / 2.0 + view.bounds.size.height / 2.0);
[newProps removeObjectForKey:@"height"];
}
if (newProps[@"width"]) {
double width = [self getDoubleOrZero:newProps[@"width"]];
double oldWidth = view.bounds.size.width;
view.bounds = CGRectMake(0, 0, width, view.bounds.size.height);
view.center = CGPointMake(view.center.x + view.bounds.size.width / 2.0 - oldWidth / 2.0, view.center.y);
[newProps removeObjectForKey:@"width"];
}
bool needsViewPositionUpdate = false;
double centerX = view.center.x;
double centerY = view.center.y;
if (newProps[@"originX"]) {
needsViewPositionUpdate = true;
double originX = [self getDoubleOrZero:newProps[@"originX"]];
[newProps removeObjectForKey:@"originX"];
centerX = originX + view.bounds.size.width / 2.0;
}
if (newProps[@"originY"]) {
needsViewPositionUpdate = true;
double originY = [self getDoubleOrZero:newProps[@"originY"]];
[newProps removeObjectForKey:@"originY"];
centerY = originY + view.bounds.size.height / 2.0;
}
if (needsViewPositionUpdate) {
CGPoint newCenter = CGPointMake(centerX, centerY);
if (convertFromAbsolute) {
#if TARGET_OS_OSX
REAUIView *window = UIApplication.sharedApplication.keyWindow;
#else
REAUIView *window = RCTKeyWindow();
#endif
CGPoint convertedCenter = [window convertPoint:newCenter toView:view.superview];
view.center = convertedCenter;
} else {
view.center = newCenter;
}
}
if (newProps[@"transformMatrix"]) {
NSArray *matrix = newProps[@"transformMatrix"];
CGFloat a = [matrix[0] floatValue];
CGFloat b = [matrix[1] floatValue];
CGFloat c = [matrix[3] floatValue];
CGFloat d = [matrix[4] floatValue];
CGFloat tx = [matrix[6] floatValue];
CGFloat ty = [matrix[7] floatValue];
view.transform = CGAffineTransformMake(a, b, c, d, tx, ty);
[newProps removeObjectForKey:@"transformMatrix"];
}
NSMutableDictionary *componentDataByName = [_uiManager valueForKey:@"_componentDataByName"];
RCTComponentData *componentData = componentDataByName[@"RCTView"];
[componentData setProps:newProps forView:view];
}
- (NSDictionary *)prepareDataForAnimatingWorklet:(NSMutableDictionary *)values frameConfig:(FrameConfigType)frameConfig
{
if (frameConfig == EnteringFrame) {
NSDictionary *preparedData = @{
@"targetWidth" : values[@"width"],
@"targetHeight" : values[@"height"],
@"targetOriginX" : values[@"originX"],
@"targetOriginY" : values[@"originY"],
@"targetGlobalOriginX" : values[@"globalOriginX"],
@"targetGlobalOriginY" : values[@"globalOriginY"],
@"windowWidth" : values[@"windowWidth"],
@"windowHeight" : values[@"windowHeight"]
};
return preparedData;
} else {
NSDictionary *preparedData = @{
@"currentWidth" : values[@"width"],
@"currentHeight" : values[@"height"],
@"currentOriginX" : values[@"originX"],
@"currentOriginY" : values[@"originY"],
@"currentGlobalOriginX" : values[@"globalOriginX"],
@"currentGlobalOriginY" : values[@"globalOriginY"],
@"windowWidth" : values[@"windowWidth"],
@"windowHeight" : values[@"windowHeight"]
};
return preparedData;
}
}
- (NSMutableDictionary *)prepareDataForLayoutAnimatingWorklet:(NSMutableDictionary *)currentValues
targetValues:(NSMutableDictionary *)targetValues
{
NSMutableDictionary *preparedData = [NSMutableDictionary new];
preparedData[@"currentWidth"] = currentValues[@"width"];
preparedData[@"currentHeight"] = currentValues[@"height"];
preparedData[@"currentOriginX"] = currentValues[@"originX"];
preparedData[@"currentOriginY"] = currentValues[@"originY"];
preparedData[@"currentGlobalOriginX"] = currentValues[@"globalOriginX"];
preparedData[@"currentGlobalOriginY"] = currentValues[@"globalOriginY"];
preparedData[@"targetWidth"] = targetValues[@"width"];
preparedData[@"targetHeight"] = targetValues[@"height"];
preparedData[@"targetOriginX"] = targetValues[@"originX"];
preparedData[@"targetOriginY"] = targetValues[@"originY"];
preparedData[@"targetGlobalOriginX"] = targetValues[@"globalOriginX"];
preparedData[@"targetGlobalOriginY"] = targetValues[@"globalOriginY"];
preparedData[@"windowWidth"] = currentValues[@"windowWidth"];
preparedData[@"windowHeight"] = currentValues[@"windowHeight"];
return preparedData;
}
- (void)registerExitingAncestors:(REAUIView *)child
{
[self registerExitingAncestors:child exitingSubviewsCount:1];
}
- (void)registerExitingAncestors:(REAUIView *)child exitingSubviewsCount:(int)exitingSubviewsCount
{
NSNumber *childTag = child.reactTag;
REAUIView *parent = child.superview;
UIViewController *childController = child.reactViewController;
// only register ancestors whose `reactViewController` is the same as `child`'s.
// The idea is that, if a whole ViewController is unmounted, we won't want to run
// the exiting animation since all the views will disappear immediately anyway
while (parent != nil && parent.reactViewController == childController &&
![parent isKindOfClass:[RCTRootView class]]) {
NSNumber *parentTag = parent.reactTag;
if (parentTag != nil) {
_exitingSubviewsCountMap[parent.reactTag] =
@([_exitingSubviewsCountMap[parent.reactTag] intValue] + exitingSubviewsCount);
_exitingParentTags[childTag] = parentTag;
childTag = parentTag;
}
parent = parent.superview;
}
}
- (void)maybeDropAncestors:(REAUIView *)child
{
REAUIView *parent = child.superview;
NSNumber *parentTag = _exitingParentTags[child.reactTag];
[_exitingParentTags removeObjectForKey:child.reactTag];
while ((parent != nil || parentTag != nil) && ![parent isKindOfClass:[RCTRootView class]]) {
REAUIView *view = parent;
NSNumber *viewTag = parentTag;
parentTag = _exitingParentTags[viewTag];
REAUIView *viewByTag = [self viewForTag:viewTag];
parent = view.superview;
if (view == nil) {
if (viewByTag == nil) {
// the view was already removed from both native and RN hierarchies
// we can safely forget that it had any animated children
[_ancestorsToRemove removeObject:viewTag];
[_exitingSubviewsCountMap removeObjectForKey:viewTag];
[_exitingParentTags removeObjectForKey:viewTag];
continue;
}
// the child was dettached from view, but view is still
// in the native and RN hierarchy
view = viewByTag;
}
if (view.reactTag == nil) {
// we skip over views with no tag when registering parent tags,
// so we shouldn't go to the parent of viewTag yet
parentTag = viewTag;
continue;
}
int trackingCount = [_exitingSubviewsCountMap[view.reactTag] intValue] - 1;
if (trackingCount <= 0) {
if ([_ancestorsToRemove containsObject:view.reactTag]) {
[_ancestorsToRemove removeObject:view.reactTag];
if (![_exitingViews objectForKey:view.reactTag]) {
[view removeFromSuperview];
}
}
[_exitingSubviewsCountMap removeObjectForKey:view.reactTag];
[_exitingParentTags removeObjectForKey:view.reactTag];
} else {
_exitingSubviewsCountMap[view.reactTag] = @(trackingCount);
}
}
}
- (BOOL)startAnimationsRecursive:(REAUIView *)view
shouldRemoveSubviewsWithoutAnimations:(BOOL)shouldRemoveSubviewsWithoutAnimations
shouldAnimate:(BOOL)shouldAnimate;
{
if (!view.reactTag) {
return NO;
}
UIViewController *viewController = view.reactViewController;
// `startAnimationsRecursive:shouldRemoveSubviewsWithoutAnimations:`
// is called on a detached view tree, so the `viewController` should be `nil`.
// If it's not, we're descending into another `UIViewController`.
// We don't want to run animations inside it (since it causes issues with RNScreens),
// so instead clean up the subtree and return `NO`.
if (viewController != nil) {
[self removeAnimationsFromSubtree:view];
return NO;
}
shouldAnimate = [self shouldAnimateExiting:view.reactTag shouldAnimate:shouldAnimate];
BOOL hasExitAnimation = shouldAnimate &&
([self hasAnimationForTag:view.reactTag type:EXITING] || [_exitingViews objectForKey:view.reactTag]);
BOOL hasAnimatedChildren = NO;
shouldRemoveSubviewsWithoutAnimations = shouldRemoveSubviewsWithoutAnimations && !hasExitAnimation;
NSMutableArray *toBeRemoved = [[NSMutableArray alloc] init];
for (REAUIView *subview in [view.reactSubviews copy]) {
if ([self startAnimationsRecursive:subview
shouldRemoveSubviewsWithoutAnimations:shouldRemoveSubviewsWithoutAnimations
shouldAnimate:shouldAnimate]) {
hasAnimatedChildren = YES;
} else if (shouldRemoveSubviewsWithoutAnimations) {
[toBeRemoved addObject:subview];
}
}
BOOL wantAnimateExit = hasExitAnimation || hasAnimatedChildren;
REASnapshot *before;
if (hasExitAnimation) {
before = [[REASnapshot alloc] init:view];
}
// start exit animation
if (hasExitAnimation && ![_exitingViews objectForKey:view.reactTag]) {
NSDictionary *preparedValues = [self prepareDataForAnimatingWorklet:before.values frameConfig:ExitingFrame];
[_exitingViews setObject:view forKey:view.reactTag];
[self registerExitingAncestors:view];
_startAnimationForTag(view.reactTag, EXITING, preparedValues);
}
// NOTE: even though this view is still visible,
// since it's removed from the React tree, we won't
// start new animations for it, and might as well remove
// the layout animation config now
_clearAnimationConfigForTag(view.reactTag);
if (!wantAnimateExit) {
return NO;
}
if (hasAnimatedChildren) {
[_ancestorsToRemove addObject:view.reactTag];
}
for (REAUIView *child in toBeRemoved) {
[view removeReactSubview:child];
}
// we don't want user interaction on exiting views
view.userInteractionEnabled = NO;
return YES;
}
- (void)reattachAnimatedChildren:(NSArray<id<RCTComponent>> *)children
toContainer:(id<RCTComponent>)container
atIndices:(NSArray<NSNumber *> *)indices
{
if (![container isKindOfClass:[REAUIView class]]) {
return;
}
// since we reattach only some of the views,
// we count the views we DIDN'T reattach
// and shift later views' indices by that number
// to make sure they appear at correct relative posisitons
// in the `subviews` array
int skippedViewsCount = 0;
for (int i = 0; i < children.count; i++) {
id<RCTComponent> child = children[i];
if (![child isKindOfClass:[REAUIView class]]) {
skippedViewsCount++;
continue;
}
REAUIView *childView = (REAUIView *)child;
NSNumber *originalIndex = indices[i];
if ([self startAnimationsRecursive:childView shouldRemoveSubviewsWithoutAnimations:YES shouldAnimate:YES]) {
[(REAUIView *)container insertSubview:childView atIndex:[originalIndex intValue] - skippedViewsCount];
int exitingSubviewsCount = [_exitingSubviewsCountMap[childView.reactTag] intValue];
if ([_exitingViews objectForKey:childView.reactTag] != nil) {
exitingSubviewsCount++;
}
[self registerExitingAncestors:childView exitingSubviewsCount:exitingSubviewsCount];
} else {
skippedViewsCount++;
}
}
}
- (void)onViewCreate:(REAUIView *)view after:(REASnapshot *)after
{
NSMutableDictionary *targetValues = after.values;
NSDictionary *preparedValues = [self prepareDataForAnimatingWorklet:targetValues frameConfig:EnteringFrame];
[_enteringViews addObject:view.reactTag];
_startAnimationForTag(view.reactTag, ENTERING, preparedValues);
}
- (void)onViewUpdate:(REAUIView *)view before:(REASnapshot *)before after:(REASnapshot *)after
{
NSMutableDictionary *targetValues = after.values;
NSMutableDictionary *currentValues = before.values;
NSDictionary *preparedValues = [self prepareDataForLayoutAnimatingWorklet:currentValues targetValues:targetValues];
_startAnimationForTag(view.reactTag, LAYOUT, preparedValues);
}
- (REASnapshot *)prepareSnapshotBeforeMountForView:(REAUIView *)view
{
return [[REASnapshot alloc] init:view];
}
- (void)removeAnimationsFromSubtree:(REAUIView *)view
{
REANodeFind(view, ^int(id<RCTComponent> view) {
if (!self->_hasAnimationForTag(view.reactTag, SHARED_ELEMENT_TRANSITION)) {
self->_clearAnimationConfigForTag(view.reactTag);
}
return false;
});
}
- (void)viewDidMount:(REAUIView *)view withBeforeSnapshot:(nonnull REASnapshot *)before withNewFrame:(CGRect)frame
{
LayoutAnimationType type = before == nil ? ENTERING : LAYOUT;
NSNumber *viewTag = view.reactTag;
if (_hasAnimationForTag(viewTag, type)) {
REASnapshot *after = [[REASnapshot alloc] init:view];
if (before == nil) {
[self onViewCreate:view after:after];
} else {
[self onViewUpdate:view before:before after:after];
}
} else if (type == LAYOUT && [_enteringViews containsObject:[view reactTag]]) {
_enteringViewTargetValues[[view reactTag]] = [[REASnapshot alloc] init:view];
[self setNewProps:before.values forView:view];
}
if (_hasAnimationForTag(viewTag, SHARED_ELEMENT_TRANSITION)) {
if (type == ENTERING) {
[_sharedTransitionManager notifyAboutNewView:view];
#ifndef NDEBUG
_checkDuplicateSharedTag(view, viewTag);
#endif
} else {
[_sharedTransitionManager notifyAboutViewLayout:view withViewFrame:frame];
}
}
}
- (void)viewsDidLayout
{
[_sharedTransitionManager viewsDidLayout];
}
- (void)setFindPrecedingViewTagForTransitionBlock:
(REAFindPrecedingViewTagForTransitionBlock)findPrecedingViewTagForTransition
{
[_sharedTransitionManager setFindPrecedingViewTagForTransitionBlock:findPrecedingViewTagForTransition];
}
- (void)setCancelAnimationBlock:(REACancelAnimationBlock)animationCancellingBlock
{
[_sharedTransitionManager setCancelAnimationBlock:animationCancellingBlock];
}
- (BOOL)hasAnimationForTag:(NSNumber *)tag type:(LayoutAnimationType)type
{
return _hasAnimationForTag(tag, type);
}
- (BOOL)shouldAnimateExiting:(NSNumber *)tag shouldAnimate:(BOOL)shouldAnimate
{
return _shouldAnimateExiting(tag, shouldAnimate);
}
- (void)clearAnimationConfigForTag:(NSNumber *)tag
{
_clearAnimationConfigForTag(tag);
}
- (void)clearSharedTransitionConfigForTag:(NSNumber *)tag
{
_clearSharedTransitionConfigForTag(tag);
}
- (void)startAnimationForTag:(NSNumber *)tag type:(LayoutAnimationType)type yogaValues:(NSDictionary *)yogaValues
{
_startAnimationForTag(tag, type, yogaValues);
}
- (void)onScreenRemoval:(REAUIView *)screen stack:(REAUIView *)stack
{
[_sharedTransitionManager onScreenRemoval:screen stack:stack];
}
@end

View File

@@ -0,0 +1,10 @@
@interface REAFrame : NSObject
@property float x;
@property float y;
@property float width;
@property float height;
- (instancetype)initWithX:(float)x y:(float)y width:(float)width height:(float)height;
@end

View File

@@ -0,0 +1,15 @@
#import <RNReanimated/REAFrame.h>
@implementation REAFrame
- (instancetype)initWithX:(float)x y:(float)y width:(float)width height:(float)height
{
self = [super init];
_x = x;
_y = y;
_width = width;
_height = height;
return self;
}
@end

View File

@@ -0,0 +1,21 @@
#define LOAD_SCREENS_HEADERS \
((!RCT_NEW_ARCH_ENABLED && __has_include(<RNScreens/RNSScreen.h>)) \
|| (RCT_NEW_ARCH_ENABLED && __has_include(<RNScreens/RNSScreen.h>) && __cplusplus))
#if LOAD_SCREENS_HEADERS
#import <RNScreens/RNSScreen.h>
#import <RNScreens/RNSScreenStack.h>
#endif
#import <RNReanimated/REAUIKit.h>
@interface REAScreensHelper : NSObject
+ (REAUIView *)getScreenForView:(REAUIView *)view;
+ (REAUIView *)getStackForView:(REAUIView *)view;
+ (bool)isScreenModal:(REAUIView *)screen;
+ (REAUIView *)getScreenWrapper:(REAUIView *)view;
+ (int)getScreenType:(REAUIView *)screen;
+ (bool)isRNSScreenType:(REAUIView *)screen;
@end

View File

@@ -0,0 +1,106 @@
#import <RNReanimated/REAScreensHelper.h>
@implementation REAScreensHelper
#if LOAD_SCREENS_HEADERS
+ (REAUIView *)getScreenForView:(REAUIView *)view
{
REAUIView *screen = view;
while (![screen isKindOfClass:[RNSScreenView class]] && screen.superview != nil) {
screen = screen.superview;
}
if ([screen isKindOfClass:[RNSScreenView class]]) {
return screen;
}
return nil;
}
+ (REAUIView *)getStackForView:(REAUIView *)view
{
if ([view isKindOfClass:[RNSScreenView class]]) {
if (view.reactSuperview != nil) {
if ([view.reactSuperview isKindOfClass:[RNSScreenStackView class]]) {
return view.reactSuperview;
}
}
}
while (view != nil && ![view isKindOfClass:[RNSScreenStackView class]] && view.superview != nil) {
view = view.superview;
}
if ([view isKindOfClass:[RNSScreenStackView class]]) {
return view;
}
return nil;
}
+ (bool)isScreenModal:(REAUIView *)uiViewScreen
{
if ([uiViewScreen isKindOfClass:[RNSScreenView class]]) {
RNSScreenView *screen = (RNSScreenView *)uiViewScreen;
bool isModal = [screen isModal];
if (!isModal) {
// case for modal with header
RNSScreenView *parentScreen = (RNSScreenView *)[REAScreensHelper getScreenForView:screen.reactSuperview];
if (parentScreen != nil) {
isModal = [parentScreen isModal];
}
}
return isModal;
}
return false;
}
+ (REAUIView *)getScreenWrapper:(REAUIView *)view
{
REAUIView *screen = [REAScreensHelper getScreenForView:view];
REAUIView *stack = [REAScreensHelper getStackForView:screen];
REAUIView *screenWrapper = [REAScreensHelper getScreenForView:stack];
return screenWrapper;
}
+ (int)getScreenType:(REAUIView *)screen;
{
return [[screen valueForKey:@"stackPresentation"] intValue];
}
+ (bool)isRNSScreenType:(REAUIView *)view
{
return [view isKindOfClass:[RNSScreen class]] == YES;
}
#else
+ (REAUIView *)getScreenForView:(REAUIView *)view
{
return nil;
}
+ (REAUIView *)getStackForView:(REAUIView *)view
{
return nil;
}
+ (bool)isScreenModal:(REAUIView *)screen
{
return false;
}
+ (REAUIView *)getScreenWrapper:(REAUIView *)view
{
return nil;
}
+ (int)getScreenType:(REAUIView *)screen;
{
return 0;
}
+ (bool)isRNSScreenType:(REAUIView *)screen
{
return false;
}
#endif // LOAD_SCREENS_HEADERS
@end

View File

@@ -0,0 +1,18 @@
#import <RNReanimated/LayoutAnimationType.h>
#import <RNReanimated/REASnapshot.h>
#import <RNReanimated/REAUIKit.h>
@interface REASharedElement : NSObject
- (instancetype)initWithSourceView:(REAUIView *)sourceView
sourceViewSnapshot:(REASnapshot *)sourceViewSnapshot
targetView:(REAUIView *)targetView
targetViewSnapshot:(REASnapshot *)targetViewSnapshot;
@property REAUIView *sourceView;
@property REASnapshot *sourceViewSnapshot;
@property REAUIView *targetView;
@property REASnapshot *targetViewSnapshot;
@property LayoutAnimationType animationType;
@end

View File

@@ -0,0 +1,17 @@
#import <RNReanimated/REASharedElement.h>
@implementation REASharedElement
- (instancetype)initWithSourceView:(REAUIView *)sourceView
sourceViewSnapshot:(REASnapshot *)sourceViewSnapshot
targetView:(REAUIView *)targetView
targetViewSnapshot:(REASnapshot *)targetViewSnapshot
{
self = [super init];
_sourceView = sourceView;
_sourceViewSnapshot = sourceViewSnapshot;
_targetView = targetView;
_targetViewSnapshot = targetViewSnapshot;
_animationType = SHARED_ELEMENT_TRANSITION;
return self;
}
@end

View File

@@ -0,0 +1,19 @@
#import <RNReanimated/REAAnimationsManager.h>
#import <RNReanimated/REASnapshot.h>
@interface REASharedTransitionManager : NSObject
- (void)notifyAboutNewView:(REAUIView *)view;
- (void)notifyAboutViewLayout:(REAUIView *)view withViewFrame:(CGRect)frame;
- (void)viewsDidLayout;
- (void)finishSharedAnimation:(REAUIView *)view removeView:(BOOL)removeView;
- (void)setFindPrecedingViewTagForTransitionBlock:
(REAFindPrecedingViewTagForTransitionBlock)findPrecedingViewTagForTransition;
- (void)setCancelAnimationBlock:(REACancelAnimationBlock)cancelAnimationBlock;
- (instancetype)initWithAnimationsManager:(REAAnimationsManager *)animationManager;
- (REAUIView *)getTransitioningView:(NSNumber *)tag;
- (NSDictionary *)prepareDataForWorklet:(NSMutableDictionary *)currentValues
targetValues:(NSMutableDictionary *)targetValues;
- (void)onScreenRemoval:(REAUIView *)screen stack:(REAUIView *)stack;
@end

View File

@@ -0,0 +1,823 @@
#import <RNReanimated/REAFrame.h>
#import <RNReanimated/REAScreensHelper.h>
#import <RNReanimated/REASharedElement.h>
#import <RNReanimated/REASharedTransitionManager.h>
#import <RNReanimated/REAUtils.h>
@implementation REASharedTransitionManager {
NSMutableDictionary<NSNumber *, REAUIView *> *_sharedTransitionParent;
NSMutableDictionary<NSNumber *, NSNumber *> *_sharedTransitionInParentIndex;
NSMutableDictionary<NSNumber *, REASnapshot *> *_snapshotRegistry;
NSMutableDictionary<NSNumber *, REAUIView *> *_currentSharedTransitionViews;
REAFindPrecedingViewTagForTransitionBlock _findPrecedingViewTagForTransition;
REACancelAnimationBlock _cancelLayoutAnimation;
REAUIView *_transitionContainer;
NSMutableArray<REAUIView *> *_addedSharedViews;
BOOL _isSharedTransitionActive;
NSMutableArray<REASharedElement *> *_sharedElements;
NSMutableDictionary<NSNumber *, REASharedElement *> *_sharedElementsLookup;
REAAnimationsManager *_animationManager;
NSMutableSet<NSNumber *> *_viewsToHide;
NSMutableArray<REAUIView *> *_removedViews;
NSMutableSet<REAUIView *> *_viewsWithCanceledAnimation;
NSMutableDictionary<NSNumber *, NSNumber *> *_disableCleaningForView;
NSMutableDictionary<NSNumber *, REAUIView *> *_removedViewRegistry;
NSMutableSet<NSNumber *> *_layoutedSharedViewsTags;
NSMutableDictionary<NSNumber *, REAFrame *> *_layoutedSharedViewsFrame;
NSMutableSet<REAUIView *> *_reattachedViews;
BOOL _isStackDropped;
BOOL _isAsyncSharedTransitionConfigured;
BOOL _isConfigured;
BOOL _clearScreen;
BOOL _isInteractive;
REAUIView *_disappearingScreen;
}
/*
`_sharedTransitionManager` provides access to current REASharedTransitionManager
instance from swizzled methods in react-native-screens. Swizzled method has
different context of execution (self != REASharedTransitionManager)
*/
static REASharedTransitionManager *_sharedTransitionManager;
- (instancetype)initWithAnimationsManager:(REAAnimationsManager *)animationManager
{
if (self = [super init]) {
_snapshotRegistry = [NSMutableDictionary new];
_currentSharedTransitionViews = [NSMutableDictionary new];
_addedSharedViews = [NSMutableArray new];
_sharedTransitionParent = [NSMutableDictionary new];
_sharedTransitionInParentIndex = [NSMutableDictionary new];
_isSharedTransitionActive = NO;
_sharedElements = [NSMutableArray new];
_sharedElementsLookup = [NSMutableDictionary new];
_animationManager = animationManager;
_viewsToHide = [NSMutableSet new];
_sharedTransitionManager = self;
_disableCleaningForView = [NSMutableDictionary new];
_removedViewRegistry = [NSMutableDictionary new];
_layoutedSharedViewsTags = [NSMutableSet new];
_layoutedSharedViewsFrame = [NSMutableDictionary new];
_reattachedViews = [NSMutableSet new];
_isAsyncSharedTransitionConfigured = NO;
_isConfigured = NO;
[self swizzleScreensMethods];
}
return self;
}
- (void)invalidate
{
_snapshotRegistry = nil;
_currentSharedTransitionViews = nil;
_addedSharedViews = nil;
_sharedTransitionParent = nil;
_sharedTransitionInParentIndex = nil;
_sharedElements = nil;
_animationManager = nil;
}
- (REAUIView *)getTransitioningView:(NSNumber *)tag
{
REAUIView *view = _currentSharedTransitionViews[tag];
if (view == nil) {
return _removedViewRegistry[tag];
}
return view;
}
- (void)notifyAboutNewView:(REAUIView *)view
{
if (!_isConfigured) {
return;
}
[_addedSharedViews addObject:view];
}
- (void)notifyAboutViewLayout:(REAUIView *)view withViewFrame:(CGRect)frame
{
if (!_isConfigured) {
return;
}
[_layoutedSharedViewsTags addObject:view.reactTag];
float x = frame.origin.x;
float y = frame.origin.y;
float width = frame.size.width;
float height = frame.size.height;
_layoutedSharedViewsFrame[view.reactTag] = [[REAFrame alloc] initWithX:x y:y width:width height:height];
}
- (void)viewsDidLayout
{
if (!_isConfigured) {
return;
}
[self configureAsyncSharedTransitionForViews:_addedSharedViews];
[_addedSharedViews removeAllObjects];
[self maybeRestartAnimationWithNewLayout];
[_layoutedSharedViewsTags removeAllObjects];
[_layoutedSharedViewsFrame removeAllObjects];
}
- (void)configureAsyncSharedTransitionForViews:(NSArray<REAUIView *> *)views
{
if ([views count] > 0) {
NSArray *sharedViews = [self sortViewsByTags:views];
_sharedElements = [self getSharedElementForCurrentTransition:sharedViews
withNewElements:YES
withOffsetX:0
withOffsetY:0];
[self resolveAnimationType:_sharedElements isInteractive:NO];
_isAsyncSharedTransitionConfigured = YES;
}
}
- (void)maybeRestartAnimationWithNewLayout
{
if ([_layoutedSharedViewsTags count] == 0 || [_currentSharedTransitionViews count] == 0) {
return;
}
NSMutableArray<REASharedElement *> *sharedElementToRestart = [NSMutableArray new];
for (REASharedElement *sharedElement in _sharedElements) {
NSNumber *viewTag = sharedElement.targetView.reactTag;
if ([_layoutedSharedViewsTags containsObject:viewTag] && _currentSharedTransitionViews[viewTag]) {
[sharedElementToRestart addObject:sharedElement];
}
}
for (REASharedElement *sharedElement in sharedElementToRestart) {
REAUIView *sourceView = sharedElement.sourceView;
REAUIView *targetView = sharedElement.targetView;
REASnapshot *newSourceViewSnapshot = [[REASnapshot alloc] initWithAbsolutePosition:sourceView];
REASnapshot *currentTargetViewSnapshot = _snapshotRegistry[targetView.reactTag];
REAFrame *frameData = _layoutedSharedViewsFrame[targetView.reactTag];
float currentOriginX = [currentTargetViewSnapshot.values[@"originX"] floatValue];
float currentOriginY = [currentTargetViewSnapshot.values[@"originY"] floatValue];
float currentOriginXByParent = [currentTargetViewSnapshot.values[@"originXByParent"] floatValue];
float currentOriginYByParent = [currentTargetViewSnapshot.values[@"originYByParent"] floatValue];
NSNumber *newOriginX = @(currentOriginX - currentOriginXByParent + frameData.x);
NSNumber *newOriginY = @(currentOriginY - currentOriginYByParent + frameData.y);
currentTargetViewSnapshot.values[@"width"] = @(frameData.width);
currentTargetViewSnapshot.values[@"height"] = @(frameData.height);
currentTargetViewSnapshot.values[@"originX"] = newOriginX;
currentTargetViewSnapshot.values[@"originY"] = newOriginY;
currentTargetViewSnapshot.values[@"globalOriginX"] = newOriginX;
currentTargetViewSnapshot.values[@"globalOriginY"] = newOriginY;
currentTargetViewSnapshot.values[@"originXByParent"] = @(frameData.x);
currentTargetViewSnapshot.values[@"originYByParent"] = @(frameData.y);
sharedElement.sourceViewSnapshot = newSourceViewSnapshot;
[self disableCleaningForViewTag:sourceView.reactTag];
[self disableCleaningForViewTag:targetView.reactTag];
}
[self startSharedTransition:sharedElementToRestart];
}
- (BOOL)configureAndStartSharedTransitionForViews:(NSArray<REAUIView *> *)views
isInteractive:(BOOL)isInteractive
withOffsetX:(double)offsetX
withOffsetY:(double)offsetY
{
NSArray *sharedViews = [self sortViewsByTags:views];
NSArray<REASharedElement *> *sharedElements = [self getSharedElementForCurrentTransition:sharedViews
withNewElements:NO
withOffsetX:offsetX
withOffsetY:offsetY];
if ([sharedElements count] == 0) {
return NO;
}
[self resolveAnimationType:sharedElements isInteractive:isInteractive];
[self configureTransitionContainer];
[self reparentSharedViewsForCurrentTransition:sharedElements];
[self startSharedTransition:sharedElements];
return YES;
}
- (NSArray *)sortViewsByTags:(NSArray *)views
{
/*
All shared views during the transition have the same parent. It is problematic if parent
view and their children are in the same transition. To keep the valid order in the z-axis,
we need to sort views by tags. Parent tag is lower than children tags.
*/
return [views sortedArrayUsingComparator:^NSComparisonResult(REAUIView *view1, REAUIView *view2) {
return [view2.reactTag compare:view1.reactTag];
}];
}
- (NSMutableArray<REASharedElement *> *)getSharedElementForCurrentTransition:(NSArray *)sharedViews
withNewElements:(BOOL)addedNewScreen
withOffsetX:(double)offsetX
withOffsetY:(double)offsetY
{
NSMutableArray<REAUIView *> *newTransitionViews = [NSMutableArray new];
NSMutableArray<REASharedElement *> *newSharedElements = [NSMutableArray new];
NSMutableSet<NSNumber *> *currentSharedViewsTags = [NSMutableSet new];
for (REAUIView *sharedView in sharedViews) {
[currentSharedViewsTags addObject:sharedView.reactTag];
}
for (REAUIView *sharedView in sharedViews) {
// add observers
REAUIView *sharedViewScreen = [REAScreensHelper getScreenForView:sharedView];
REAUIView *stack = [REAScreensHelper getStackForView:sharedViewScreen];
// find sibling for shared view
NSNumber *siblingViewTag = _findPrecedingViewTagForTransition(sharedView.reactTag);
REAUIView *siblingView = nil;
do {
siblingView = [_animationManager viewForTag:siblingViewTag];
if (siblingView == nil) {
[self clearAllSharedConfigsForViewTag:siblingViewTag];
siblingViewTag = _findPrecedingViewTagForTransition(sharedView.reactTag);
}
} while (siblingView == nil && siblingViewTag != nil);
if (siblingView == nil) {
// the sibling of shared view doesn't exist yet
continue;
}
REAUIView *viewSource;
REAUIView *viewTarget;
if (addedNewScreen) {
viewSource = siblingView;
viewTarget = sharedView;
} else {
viewSource = sharedView;
viewTarget = siblingView;
}
bool isInCurrentTransition = false;
if (_currentSharedTransitionViews[viewSource.reactTag] || _currentSharedTransitionViews[viewTarget.reactTag]) {
isInCurrentTransition = true;
if (addedNewScreen) {
siblingViewTag = _findPrecedingViewTagForTransition(siblingView.reactTag);
siblingView = [_animationManager viewForTag:siblingViewTag];
viewSource = siblingView;
viewTarget = sharedView;
}
}
if ([currentSharedViewsTags containsObject:viewSource.reactTag] &&
[currentSharedViewsTags containsObject:viewTarget.reactTag]) {
continue;
}
bool isModal = [REAScreensHelper isScreenModal:sharedViewScreen];
// check valid target screen configuration
int screensCount = [stack.reactSubviews count];
if (addedNewScreen && !isModal) {
// is under top
if (screensCount < 2) {
continue;
}
REAUIView *viewSourceParentScreen = [REAScreensHelper getScreenForView:viewSource];
REAUIView *screenUnderStackTop = stack.reactSubviews[screensCount - 2];
if (![screenUnderStackTop.reactTag isEqual:viewSourceParentScreen.reactTag] && !isInCurrentTransition) {
continue;
}
} else if (!addedNewScreen && !isModal) {
// is on top
REAUIView *viewTargetParentScreen = [REAScreensHelper getScreenForView:viewTarget];
// TODO macOS navigationController isn't available on macOS
#if !TARGET_OS_OSX
REAUIView *stackTarget = viewTargetParentScreen.reactViewController.navigationController.topViewController.view;
if (stackTarget != viewTargetParentScreen) {
continue;
}
#endif
}
if (isModal) {
[_viewsToHide addObject:viewSource.reactTag];
}
REASnapshot *sourceViewSnapshot;
if (!addedNewScreen) {
sourceViewSnapshot = _snapshotRegistry[viewSource.reactTag];
} else {
sourceViewSnapshot = [[REASnapshot alloc] initWithAbsolutePosition:viewSource];
}
if (addedNewScreen && !_currentSharedTransitionViews[viewSource.reactTag]) {
_snapshotRegistry[viewSource.reactTag] = sourceViewSnapshot;
}
REASnapshot *targetViewSnapshot;
if (addedNewScreen) {
targetViewSnapshot = [[REASnapshot alloc] initWithAbsolutePosition:viewTarget];
_snapshotRegistry[viewTarget.reactTag] = targetViewSnapshot;
} else {
targetViewSnapshot = [[REASnapshot alloc] initWithAbsolutePosition:viewTarget
withOffsetX:offsetX
withOffsetY:offsetY];
}
[newTransitionViews addObject:viewSource];
[newTransitionViews addObject:viewTarget];
REASharedElement *sharedElement = [[REASharedElement alloc] initWithSourceView:viewSource
sourceViewSnapshot:sourceViewSnapshot
targetView:viewTarget
targetViewSnapshot:targetViewSnapshot];
[newSharedElements addObject:sharedElement];
}
if ([newTransitionViews count] > 0) {
NSMutableArray *currentSourceViews = [NSMutableArray new];
for (REASharedElement *sharedElement in _sharedElements) {
[currentSourceViews addObject:sharedElement.sourceView];
}
NSMutableSet *newSourceViews = [NSMutableSet new];
for (REASharedElement *sharedElement in newSharedElements) {
[newSourceViews addObject:sharedElement.sourceView];
}
for (REAUIView *view in currentSourceViews) {
if (![newSourceViews containsObject:view]) {
_removedViewRegistry[view.reactTag] = view;
}
}
[_currentSharedTransitionViews removeAllObjects];
for (REAUIView *view in newTransitionViews) {
_currentSharedTransitionViews[view.reactTag] = view;
}
}
if ([newSharedElements count] != 0) {
_sharedElements = newSharedElements;
for (REASharedElement *sharedElement in newSharedElements) {
_sharedElementsLookup[sharedElement.sourceView.reactTag] = sharedElement;
}
}
return newSharedElements;
}
/*
Method swizzling is used to get notification from react-native-screens
about push or pop screen from stack.
*/
- (void)swizzleScreensMethods
{
#if LOAD_SCREENS_HEADERS
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL viewDidLayoutSubviewsSelector = @selector(viewDidLayoutSubviews);
SEL notifyWillDisappearSelector = @selector(notifyWillDisappear);
SEL viewIsAppearingSelector = @selector(viewIsAppearing:);
Class screenClass = [RNSScreen class];
Class screenViewClass = [RNSScreenView class];
BOOL allSelectorsAreAvailable = [RNSScreen instancesRespondToSelector:viewDidLayoutSubviewsSelector] &&
[RNSScreenView instancesRespondToSelector:notifyWillDisappearSelector] &&
[RNSScreen instancesRespondToSelector:viewIsAppearingSelector] &&
[RNSScreenView instancesRespondToSelector:@selector(isModal)]; // used by REAScreenHelper
if (allSelectorsAreAvailable) {
[REAUtils swizzleMethod:viewDidLayoutSubviewsSelector
forClass:screenClass
with:@selector(reanimated_viewDidLayoutSubviews)
fromClass:[self class]];
[REAUtils swizzleMethod:notifyWillDisappearSelector
forClass:screenViewClass
with:@selector(reanimated_notifyWillDisappear)
fromClass:[self class]];
[REAUtils swizzleMethod:viewIsAppearingSelector
forClass:screenClass
with:@selector(reanimated_viewIsAppearing:)
fromClass:[self class]];
_isConfigured = YES;
}
});
#endif
}
- (void)setDisappearingScreen:(REAUIView *)view
{
_disappearingScreen = view;
_isInteractive = [_sharedTransitionManager isInteractiveScreenChange:view];
}
- (REAUIView *)getDisappearingScreen
{
return _disappearingScreen;
}
- (void)setIsInteractive:(BOOL)isInteractive
{
_isInteractive = isInteractive;
}
- (BOOL)getIsInteractive
{
return _isInteractive;
}
- (void)reanimated_viewDidLayoutSubviews
{
// call original method from react-native-screens, self == RNScreen
[self reanimated_viewDidLayoutSubviews];
REAUIView *screen = [self valueForKey:@"screenView"];
[_sharedTransitionManager screenAddedToStack:screen];
}
- (void)reanimated_notifyWillDisappear
{
// call original method from react-native-screens, self == RNSScreenView
[self reanimated_notifyWillDisappear];
[_sharedTransitionManager makeSnapshotForScreenViews:(REAUIView *)self];
bool isModal = [REAScreensHelper isScreenModal:(REAUIView *)self];
if (isModal) {
[_sharedTransitionManager setIsInteractive:[_sharedTransitionManager isInteractiveScreenChange:(REAUIView *)self]];
[_sharedTransitionManager screenRemovedFromStack:(REAUIView *)self withOffsetX:0 withOffsetY:0];
} else {
[_sharedTransitionManager setDisappearingScreen:(REAUIView *)self];
}
}
- (void)reanimated_viewIsAppearing:(BOOL)animated
{
// call original method from react-native-screens, self == RNSScreen
[self reanimated_viewIsAppearing:animated];
REAUIView *disappearingScreen = [_sharedTransitionManager getDisappearingScreen];
REAUIView *targetScreen = [self valueForKey:@"screenView"];
if (disappearingScreen != NULL) {
[_sharedTransitionManager screenRemovedFromStack:disappearingScreen
withOffsetX:-targetScreen.superview.frame.origin.x
withOffsetY:-targetScreen.superview.frame.origin.y];
}
[_sharedTransitionManager setDisappearingScreen:NULL];
}
- (void)screenAddedToStack:(REAUIView *)screen
{
if (screen.superview != nil) {
[self runAsyncSharedTransition];
}
}
- (void)screenRemovedFromStack:(REAUIView *)screen withOffsetX:(double)offsetX withOffsetY:(double)offsetY
{
_isStackDropped = NO;
REAUIView *stack = [REAScreensHelper getStackForView:screen];
bool isModal = [REAScreensHelper isScreenModal:screen];
bool isRemovedInParentStack = [self isRemovedFromHigherStack:screen];
bool isInteractive = [self getIsInteractive];
if ((stack != nil || isModal) && !isRemovedInParentStack) {
// screen is removed from React tree (navigation.navigate(<screenName>))
bool isScreenRemovedFromReactTree = [self isScreen:screen outsideStack:stack];
// click on button goBack on native header
bool isTriggeredByGoBackButton = [self isScreen:screen onTopOfStack:stack];
bool shouldRunTransition = (isScreenRemovedFromReactTree || isTriggeredByGoBackButton) &&
!(isInteractive && [_currentSharedTransitionViews count] > 0);
if (shouldRunTransition) {
[self runSharedTransitionForSharedViewsOnScreen:screen
isInteractive:isInteractive
withOffsetX:offsetX
withOffsetY:offsetY];
} else {
[self makeSnapshotForScreenViews:screen];
}
} else {
// removed stack
if (!isInteractive) {
[self clearConfigForStackNow:stack];
} else {
_isStackDropped = YES;
}
}
}
- (bool)isInteractiveScreenChange:(REAUIView *)screen
{
#if !TARGET_OS_OSX
return screen.reactViewController.transitionCoordinator.interactive;
#else
// TODO macOS transitionCoordinator isn't available on macOS
return false;
#endif
}
- (void)makeSnapshotForScreenViews:(REAUIView *)screen
{
REANodeFind(screen, ^int(id<RCTComponent> view) {
NSNumber *viewTag = view.reactTag;
if (self->_currentSharedTransitionViews[viewTag]) {
return false;
}
if ([self->_animationManager hasAnimationForTag:viewTag type:SHARED_ELEMENT_TRANSITION]) {
REASnapshot *snapshot = [[REASnapshot alloc] initWithAbsolutePosition:(REAUIView *)view
withOffsetX:0
withOffsetY:0];
self->_snapshotRegistry[viewTag] = snapshot;
}
return false;
});
}
- (void)clearConfigForStackNow:(REAUIView *)stack
{
for (REAUIView *screen in stack.reactSubviews) {
[self clearConfigForScreen:screen];
}
}
- (BOOL)isScreen:(REAUIView *)screen outsideStack:(REAUIView *)stack
{
for (REAUIView *child in stack.reactSubviews) {
if ([child.reactTag isEqual:screen.reactTag]) {
return NO;
}
}
return YES;
}
- (BOOL)isScreen:(REAUIView *)screen onTopOfStack:(REAUIView *)stack
{
int screenCount = stack.reactSubviews.count;
return screenCount > 0 && screen == stack.reactSubviews.lastObject;
}
- (BOOL)isRemovedFromHigherStack:(REAUIView *)screen
{
REAUIView *stack = screen.reactSuperview;
while (stack != nil) {
#if !TARGET_OS_OSX
screen = stack.reactViewController.navigationController.topViewController.view;
#else
// TODO macOS navigationController isn't available on macOS
screen = nil;
#endif
if (screen == nil) {
break;
}
if (screen.superview == nil) {
return YES;
}
stack = screen.reactSuperview;
}
return NO;
}
- (void)runSharedTransitionForSharedViewsOnScreen:(REAUIView *)screen
isInteractive:(BOOL)isInteractive
withOffsetX:(double)offsetX
withOffsetY:(double)offsetY
{
NSMutableArray<REAUIView *> *removedViews = [NSMutableArray new];
REANodeFind(screen, ^int(id<RCTComponent> view) {
if ([self->_animationManager hasAnimationForTag:view.reactTag type:SHARED_ELEMENT_TRANSITION]) {
[removedViews addObject:(REAUIView *)view];
}
return false;
});
BOOL startedAnimation = [self configureAndStartSharedTransitionForViews:removedViews
isInteractive:isInteractive
withOffsetX:offsetX
withOffsetY:offsetY];
if (startedAnimation) {
_removedViews = removedViews;
} else if (![self isInteractiveScreenChange:screen]) {
[self clearConfigForScreen:screen];
} else {
_clearScreen = YES;
}
}
- (void)runAsyncSharedTransition
{
if ([_sharedElements count] == 0 || !_isAsyncSharedTransitionConfigured) {
return;
}
for (REASharedElement *sharedElement in _sharedElements) {
REAUIView *viewTarget = sharedElement.targetView;
REASnapshot *targetViewSnapshot = [[REASnapshot alloc] initWithAbsolutePosition:viewTarget];
_snapshotRegistry[viewTarget.reactTag] = targetViewSnapshot;
sharedElement.targetViewSnapshot = targetViewSnapshot;
}
[self configureTransitionContainer];
[self reparentSharedViewsForCurrentTransition:_sharedElements];
[self startSharedTransition:_sharedElements];
[_addedSharedViews removeAllObjects];
_isAsyncSharedTransitionConfigured = NO;
}
- (void)configureTransitionContainer
{
if (!_isSharedTransitionActive) {
_isSharedTransitionActive = YES;
#if TARGET_OS_OSX
REAUIView *mainWindow = UIApplication.sharedApplication.keyWindow;
#else
REAUIView *mainWindow = (REAUIView *)RCTKeyWindow();
#endif
if (_transitionContainer == nil) {
_transitionContainer = [REAUIView new];
}
[mainWindow addSubview:_transitionContainer];
// TODO macOS bringSubviewToFront isn't available on macOS
#if !TARGET_OS_OSX
[mainWindow bringSubviewToFront:_transitionContainer];
#endif
}
}
- (void)reparentSharedViewsForCurrentTransition:(NSArray *)sharedElements
{
for (REASharedElement *sharedElement in sharedElements) {
REAUIView *viewSource = sharedElement.sourceView;
[_reattachedViews addObject:viewSource];
if (_sharedTransitionParent[viewSource.reactTag] == nil) {
_sharedTransitionParent[viewSource.reactTag] = viewSource.superview;
_sharedTransitionInParentIndex[viewSource.reactTag] = @([viewSource.superview.subviews indexOfObject:viewSource]);
[viewSource removeFromSuperview];
[_transitionContainer addSubview:viewSource];
}
}
}
- (void)startSharedTransition:(NSArray *)sharedElements
{
for (REASharedElement *sharedElement in sharedElements) {
sharedElement.targetView.hidden = YES;
LayoutAnimationType type = sharedElement.animationType;
[self onViewTransition:sharedElement.sourceView
before:sharedElement.sourceViewSnapshot
after:sharedElement.targetViewSnapshot
type:type];
}
}
- (void)onViewTransition:(REAUIView *)view
before:(REASnapshot *)before
after:(REASnapshot *)after
type:(LayoutAnimationType)type
{
NSMutableDictionary *targetValues = after.values;
NSMutableDictionary *currentValues = before.values;
// TODO macOS bringSubviewToFront isn't available on macOS
#if !TARGET_OS_OSX
[view.superview bringSubviewToFront:view];
#endif
NSDictionary *preparedValues = [self prepareDataForWorklet:currentValues targetValues:targetValues];
[_animationManager startAnimationForTag:view.reactTag type:type yogaValues:preparedValues];
}
- (void)finishSharedAnimation:(REAUIView *)view removeView:(BOOL)removeView
{
if (!_isConfigured) {
return;
}
NSNumber *viewTag = view.reactTag;
if (_disableCleaningForView[viewTag]) {
[self enableCleaningForViewTag:viewTag];
return;
}
REASharedElement *sharedElement = _sharedElementsLookup[viewTag];
if (sharedElement == nil) {
return;
}
[_sharedElementsLookup removeObjectForKey:viewTag];
if ([_reattachedViews containsObject:view]) {
[_reattachedViews removeObject:view];
[view removeFromSuperview];
REAUIView *parent = _sharedTransitionParent[viewTag];
int childIndex = [_sharedTransitionInParentIndex[viewTag] intValue];
REAUIView *screen = [REAScreensHelper getScreenForView:parent];
bool isScreenInReactTree = screen.reactSuperview != nil;
if (isScreenInReactTree) {
[parent insertSubview:view atIndex:childIndex];
REASnapshot *viewSourcePreviousSnapshot = _snapshotRegistry[viewTag];
[_animationManager progressLayoutAnimationWithStyle:viewSourcePreviousSnapshot.values
forTag:viewTag
isSharedTransition:YES];
float originXByParent = [viewSourcePreviousSnapshot.values[@"originXByParent"] floatValue];
float originYByParent = [viewSourcePreviousSnapshot.values[@"originYByParent"] floatValue];
float height = [viewSourcePreviousSnapshot.values[@"height"] floatValue];
float width = [viewSourcePreviousSnapshot.values[@"width"] floatValue];
[view setCenter:CGPointMake(originXByParent + width / 2.0, originYByParent + height / 2.0)];
}
[_sharedTransitionParent removeObjectForKey:viewTag];
[_sharedTransitionInParentIndex removeObjectForKey:viewTag];
}
REAUIView *targetView = sharedElement.targetView;
targetView.hidden = NO;
if ([_viewsToHide containsObject:viewTag]) {
view.hidden = YES;
}
if (!removeView) {
[_removedViews removeObject:view];
}
if ([_removedViews containsObject:view]) {
[_animationManager clearSharedTransitionConfigForTag:viewTag];
}
if (_removedViewRegistry[view.reactTag]) {
return;
}
if ([_reattachedViews count] == 0) {
[_transitionContainer removeFromSuperview];
[_removedViewRegistry removeAllObjects];
[_currentSharedTransitionViews removeAllObjects];
[_removedViews removeAllObjects];
[_sharedElements removeAllObjects];
[_sharedElementsLookup removeAllObjects];
[_viewsToHide removeAllObjects];
_isSharedTransitionActive = NO;
}
}
- (void)setFindPrecedingViewTagForTransitionBlock:
(REAFindPrecedingViewTagForTransitionBlock)findPrecedingViewTagForTransition
{
_findPrecedingViewTagForTransition = findPrecedingViewTagForTransition;
}
- (void)setCancelAnimationBlock:(REACancelAnimationBlock)cancelAnimationBlock
{
_cancelLayoutAnimation = cancelAnimationBlock;
}
- (void)clearAllSharedConfigsForViewTag:(NSNumber *)viewTag
{
if (viewTag != nil) {
[_snapshotRegistry removeObjectForKey:viewTag];
[_animationManager clearSharedTransitionConfigForTag:viewTag];
}
}
- (void)cancelAnimation:(NSNumber *)viewTag
{
_cancelLayoutAnimation(viewTag);
}
- (void)disableCleaningForViewTag:(NSNumber *)viewTag
{
NSNumber *counter = _disableCleaningForView[viewTag];
if (counter != nil) {
_disableCleaningForView[viewTag] = @([counter intValue] + 1);
} else {
_disableCleaningForView[viewTag] = @(1);
}
}
- (void)enableCleaningForViewTag:(NSNumber *)viewTag
{
NSNumber *counter = _disableCleaningForView[viewTag];
if (counter == nil) {
return;
}
int counterInt = [counter intValue];
if (counterInt == 1) {
[_disableCleaningForView removeObjectForKey:viewTag];
} else {
_disableCleaningForView[viewTag] = @(counterInt - 1);
}
}
- (void)resolveAnimationType:(NSArray<REASharedElement *> *)sharedElements isInteractive:(BOOL)isInteractive
{
for (REASharedElement *sharedElement in sharedElements) {
NSNumber *viewTag = sharedElement.sourceView.reactTag;
bool viewHasProgressAnimation = [self->_animationManager hasAnimationForTag:viewTag
type:SHARED_ELEMENT_TRANSITION_PROGRESS];
if (viewHasProgressAnimation || isInteractive) {
sharedElement.animationType = SHARED_ELEMENT_TRANSITION_PROGRESS;
} else {
sharedElement.animationType = SHARED_ELEMENT_TRANSITION;
}
}
}
- (NSDictionary *)prepareDataForWorklet:(NSMutableDictionary *)currentValues
targetValues:(NSMutableDictionary *)targetValues
{
NSMutableDictionary *workletValues = [_animationManager prepareDataForLayoutAnimatingWorklet:currentValues
targetValues:targetValues];
workletValues[@"currentTransformMatrix"] = currentValues[@"combinedTransformMatrix"];
workletValues[@"targetTransformMatrix"] = targetValues[@"combinedTransformMatrix"];
workletValues[@"currentBorderRadius"] = currentValues[@"borderRadius"];
workletValues[@"targetBorderRadius"] = targetValues[@"borderRadius"];
return workletValues;
}
- (void)onScreenRemoval:(REAUIView *)screen stack:(REAUIView *)stack
{
if (_isStackDropped && screen != nil) {
// to clear config from stack after swipe back
[self clearConfigForStackNow:stack];
_isStackDropped = NO;
} else if (_clearScreen) {
// to clear config from screen after swipe back
[self clearConfigForScreen:screen];
_clearScreen = NO;
}
}
- (void)clearConfigForScreen:(REAUIView *)screen
{
REANodeFind(screen, ^int(id<RCTComponent> _Nonnull view) {
[self clearAllSharedConfigsForViewTag:view.reactTag];
return false;
});
}
@end

View File

@@ -0,0 +1,16 @@
#import <Foundation/Foundation.h>
#import <RNReanimated/REAUIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface REASnapshot : NSObject
@property NSMutableDictionary *values;
- (instancetype)init:(REAUIView *)view;
- (instancetype)initWithAbsolutePosition:(REAUIView *)view;
- (instancetype)initWithAbsolutePosition:(REAUIView *)view withOffsetX:(double)offsetX withOffsetY:(double)offsetY;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,180 @@
#import <Foundation/Foundation.h>
#import <RNReanimated/REAScreensHelper.h>
#import <RNReanimated/REASnapshot.h>
#import <React/RCTUtils.h>
#import <React/RCTView.h>
#import <React/UIView+React.h>
NS_ASSUME_NONNULL_BEGIN
@implementation REASnapshot
const int ScreenStackPresentationModal = 1; // RNSScreenStackPresentationModal
const int DEFAULT_MODAL_TOP_OFFSET = 69; // Default iOS modal is shifted from screen top edge by 69px
- (instancetype)init:(REAUIView *)view
{
self = [super init];
[self makeSnapshotForView:view useAbsolutePositionOnly:NO withOffsetX:0 withOffsetY:0];
return self;
}
- (instancetype)initWithAbsolutePosition:(REAUIView *)view withOffsetX:(double)offsetX withOffsetY:(double)offsetY
{
self = [super init];
[self makeSnapshotForView:view useAbsolutePositionOnly:YES withOffsetX:offsetX withOffsetY:offsetY];
return self;
}
- (instancetype)initWithAbsolutePosition:(REAUIView *)view
{
self = [super init];
[self makeSnapshotForView:view useAbsolutePositionOnly:YES withOffsetX:0 withOffsetY:0];
return self;
}
- (void)makeSnapshotForView:(REAUIView *)view
useAbsolutePositionOnly:(BOOL)useAbsolutePositionOnly
withOffsetX:(double)offsetX
withOffsetY:(double)offsetY
{
#if TARGET_OS_OSX
REAUIView *mainWindow = UIApplication.sharedApplication.keyWindow;
#else
REAUIView *mainWindow = RCTKeyWindow();
#endif
CGPoint absolutePosition = [[view superview] convertPoint:view.center toView:mainWindow];
_values = [NSMutableDictionary new];
#if TARGET_OS_OSX
_values[@"windowWidth"] = [NSNumber numberWithDouble:mainWindow.frame.size.width];
_values[@"windowHeight"] = [NSNumber numberWithDouble:mainWindow.frame.size.height];
#else
_values[@"windowWidth"] = [NSNumber numberWithDouble:mainWindow.bounds.size.width];
_values[@"windowHeight"] = [NSNumber numberWithDouble:mainWindow.bounds.size.height];
#endif
_values[@"width"] = [NSNumber numberWithDouble:(double)(view.bounds.size.width)];
_values[@"height"] = [NSNumber numberWithDouble:(double)(view.bounds.size.height)];
_values[@"globalOriginX"] = [NSNumber numberWithDouble:offsetX + absolutePosition.x - view.bounds.size.width / 2.0];
_values[@"globalOriginY"] = [NSNumber numberWithDouble:offsetY + absolutePosition.y - view.bounds.size.height / 2.0];
if (useAbsolutePositionOnly) {
_values[@"originX"] = _values[@"globalOriginX"];
_values[@"originY"] = _values[@"globalOriginY"];
_values[@"originXByParent"] = [NSNumber numberWithDouble:view.center.x - view.bounds.size.width / 2.0];
_values[@"originYByParent"] = [NSNumber numberWithDouble:view.center.y - view.bounds.size.height / 2.0];
#if TARGET_OS_OSX
REAUIView *header = nil;
#else
REAUIView *navigationContainer = view.reactViewController.navigationController.view;
REAUIView *header = [navigationContainer.subviews count] > 1 ? navigationContainer.subviews[1] : nil;
#endif
if (header != nil) {
CGFloat headerHeight = header.frame.size.height;
CGFloat headerOriginY = header.frame.origin.y;
REAUIView *screen = [REAScreensHelper getScreenForView:view];
if ([REAScreensHelper isScreenModal:screen] && screen.superview == nil) {
int additionalModalOffset = 0;
REAUIView *screenWrapper = [REAScreensHelper getScreenWrapper:view];
int screenType = [REAScreensHelper getScreenType:screenWrapper];
if (screenType == ScreenStackPresentationModal) {
additionalModalOffset = DEFAULT_MODAL_TOP_OFFSET;
}
float originY = [_values[@"originY"] doubleValue] + headerHeight + headerOriginY + additionalModalOffset;
_values[@"originY"] = @(originY);
}
_values[@"headerHeight"] = @(headerHeight);
} else {
_values[@"headerHeight"] = @(0);
}
// store the transofrmMatrix of this view, so that we can reestablish it later
CGAffineTransform transform = view.transform;
_values[@"transformMatrix"] = @[
@(transform.a),
@(transform.b),
@(0),
@(transform.c),
@(transform.d),
@(0),
@(transform.tx),
@(transform.ty),
@(1)
];
transform = [self findCombinedTransform:view];
_values[@"combinedTransformMatrix"] = @[
@(transform.a),
@(transform.b),
@(0),
@(transform.c),
@(transform.d),
@(0),
@(transform.tx),
@(transform.ty),
@(1)
];
REAUIView *transformedView = [self maybeFindTransitionView:view];
if (transformedView != nil) {
// iOS affine matrix: https://developer.apple.com/documentation/corefoundation/cgaffinetransform
transform = transformedView.transform;
// revert the transformation that was applied to the view when transition started, since we are intereseted only
// in the final result of the transition
CGPoint center = [[view superview] convertPoint:view.center toView:transformedView.superview];
CGPoint parentCenter = transformedView.center;
CGFloat x = center.x, y = center.y, a = transform.a, b = transform.b, c = transform.c, d = transform.d,
tx = transform.tx, ty = transform.ty, parentX = parentCenter.x, parentY = parentCenter.y;
center.x = (b - a) * (x - parentX - tx) / (b * c - a * d) + parentX;
center.y = (d - c) * (y - parentY - ty) / (a * d - b * c) + parentY;
CGPoint absolute = [[transformedView superview] convertPoint:center toView:nil];
_values[@"originX"] = [NSNumber numberWithDouble:offsetX + absolute.x - view.bounds.size.width / 2.0];
_values[@"originY"] = [NSNumber numberWithDouble:offsetY + absolute.y - view.bounds.size.height / 2.0];
}
#if defined(RCT_NEW_ARCH_ENABLED) || TARGET_OS_TV
_values[@"borderRadius"] = @(0);
#else
if ([view respondsToSelector:@selector(borderRadius)]) {
// For example `RCTTextView` doesn't have `borderRadius` selector
_values[@"borderRadius"] = @(((RCTView *)view).borderRadius);
} else {
_values[@"borderRadius"] = @(0);
}
#endif
} else {
_values[@"originX"] = @(view.center.x - view.bounds.size.width / 2.0);
_values[@"originY"] = @(view.center.y - view.bounds.size.height / 2.0);
}
}
- (CGAffineTransform)findCombinedTransform:(REAUIView *)view
{
CGAffineTransform transform = view.transform;
view = view.superview;
while (view != nil && ![REAScreensHelper isRNSScreenType:view]) {
// ignore transforms that are caused by transitions
if (![view.superview isKindOfClass:NSClassFromString(@"UITransitionView")]) {
CGAffineTransform t = view.transform;
// we are ignoring translations in superviews, since the positioning obtained by converting the center point (and
// applying it's view transforms) to the main window is already correct, here we only care about scale, shear and
// rotation
transform = CGAffineTransformConcat(transform, CGAffineTransformMake(t.a, t.b, t.c, t.d, 0, 0));
}
view = view.superview;
}
return transform;
}
- (REAUIView *)maybeFindTransitionView:(REAUIView *)view
{
while (view != nil && ![REAScreensHelper isRNSScreenType:view]) {
if ([view.superview isKindOfClass:NSClassFromString(@"UITransitionView")]) {
return view;
}
view = view.superview;
}
return nil;
}
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,6 @@
#import <RNReanimated/REAAnimationsManager.h>
@interface REASwizzledUIManager : NSObject
- (instancetype)initWithUIManager:(RCTUIManager *)uiManager
withAnimationManager:(REAAnimationsManager *)animationsManager;
@end

View File

@@ -0,0 +1,382 @@
#import <RNReanimated/FeaturesConfig.h>
#import <RNReanimated/REASwizzledUIManager.h>
#import <RNReanimated/REAUIKit.h>
#import <RNReanimated/REAUtils.h>
#import <React/RCTLayoutAnimation.h>
#import <React/RCTLayoutAnimationGroup.h>
#import <React/RCTRootShadowView.h>
#import <React/RCTRootViewInternal.h>
#import <React/RCTUIManager.h>
#import <React/RCTUIManagerUtils.h>
#import <objc/runtime.h>
@interface RCTUIManager (Reanimated)
@property REAAnimationsManager *animationsManager;
- (NSArray<id<RCTComponent>> *)_childrenToRemoveFromContainer:(id<RCTComponent>)container
atIndices:(NSArray<NSNumber *> *)atIndices;
@end
@implementation RCTUIManager (Reanimated)
@dynamic animationsManager;
- (void)setAnimationsManager:(REAAnimationsManager *)animationsManager
{
objc_setAssociatedObject(self, @selector(animationsManager), animationsManager, OBJC_ASSOCIATION_RETAIN);
}
- (id)animationsManager
{
return objc_getAssociatedObject(self, @selector(animationsManager));
}
@end
@implementation REASwizzledUIManager
std::atomic<uint> isFlushingBlocks;
std::atomic<bool> hasPendingBlocks;
- (instancetype)initWithUIManager:(RCTUIManager *)uiManager
withAnimationManager:(REAAnimationsManager *)animationsManager
{
if (self = [super init]) {
isFlushingBlocks = 0;
hasPendingBlocks = false;
[uiManager setAnimationsManager:animationsManager];
[self swizzleMethods];
IMP isExecutingUpdatesBatchImpl = imp_implementationWithBlock(^() {
return hasPendingBlocks || isFlushingBlocks > 0;
});
class_addMethod([RCTUIManager class], @selector(isExecutingUpdatesBatch), isExecutingUpdatesBatchImpl, "");
}
return self;
}
- (void)swizzleMethods
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[REAUtils swizzleMethod:@selector(uiBlockWithLayoutUpdateForRootView:)
forClass:[RCTUIManager class]
with:@selector(reanimated_uiBlockWithLayoutUpdateForRootView:)
fromClass:[self class]];
SEL manageChildrenOriginal = @selector
(_manageChildren:moveFromIndices:moveToIndices:addChildReactTags:addAtIndices:removeAtIndices:registry:);
SEL manageChildrenReanimated =
@selector(reanimated_manageChildren:
moveFromIndices:moveToIndices:addChildReactTags:addAtIndices:removeAtIndices:registry:);
[REAUtils swizzleMethod:manageChildrenOriginal
forClass:[RCTUIManager class]
with:manageChildrenReanimated
fromClass:[self class]];
[REAUtils swizzleMethod:@selector(addUIBlock:)
forClass:[RCTUIManager class]
with:@selector(reanimated_addUIBlock:)
fromClass:[self class]];
[REAUtils swizzleMethod:@selector(prependUIBlock:)
forClass:[RCTUIManager class]
with:@selector(reanimated_prependUIBlock:)
fromClass:[self class]];
[REAUtils swizzleMethod:@selector(flushUIBlocksWithCompletion:)
forClass:[RCTUIManager class]
with:@selector(reanimated_flushUIBlocksWithCompletion:)
fromClass:[self class]];
});
}
- (void)reanimated_manageChildren:(NSNumber *)containerTag
moveFromIndices:(NSArray<NSNumber *> *)moveFromIndices
moveToIndices:(NSArray<NSNumber *> *)moveToIndices
addChildReactTags:(NSArray<NSNumber *> *)addChildReactTags
addAtIndices:(NSArray<NSNumber *> *)addAtIndices
removeAtIndices:(NSArray<NSNumber *> *)removeAtIndices
registry:(NSMutableDictionary<NSNumber *, id<RCTComponent>> *)registry
{
bool isLayoutAnimationEnabled = reanimated::FeaturesConfig::isLayoutAnimationEnabled();
id<RCTComponent> container;
NSArray<id<RCTComponent>> *permanentlyRemovedChildren;
BOOL containerIsRootOfViewController = NO;
RCTUIManager *originalSelf = (RCTUIManager *)self;
if (isLayoutAnimationEnabled) {
container = registry[containerTag];
permanentlyRemovedChildren = [originalSelf _childrenToRemoveFromContainer:container atIndices:removeAtIndices];
if ([container isKindOfClass:[REAUIView class]]) {
UIViewController *controller = ((REAUIView *)container).reactViewController;
UIViewController *parentController = ((REAUIView *)container).superview.reactViewController;
containerIsRootOfViewController = controller != parentController;
}
// we check if the container we`re removing from is a root view
// of some view controller. In that case, we skip running exiting animations
// in its children, to prevent issues with RN Screens.
if (containerIsRootOfViewController) {
NSArray<id<RCTComponent>> *permanentlyRemovedChildren =
[originalSelf _childrenToRemoveFromContainer:container atIndices:removeAtIndices];
for (REAUIView *view in permanentlyRemovedChildren) {
[originalSelf.animationsManager endAnimationsRecursive:view];
[originalSelf.animationsManager removeAnimationsFromSubtree:view];
}
[originalSelf.animationsManager onScreenRemoval:(REAUIView *)permanentlyRemovedChildren[0]
stack:(REAUIView *)container];
}
}
// call original method
[self reanimated_manageChildren:containerTag
moveFromIndices:moveFromIndices
moveToIndices:moveToIndices
addChildReactTags:addChildReactTags
addAtIndices:addAtIndices
removeAtIndices:removeAtIndices
registry:registry];
if (!isLayoutAnimationEnabled) {
return;
}
if (containerIsRootOfViewController) {
return;
}
// we sort the (index, view) pairs to make sure we insert views back in order
NSMutableArray<NSArray<id> *> *removedViewsWithIndices = [NSMutableArray new];
for (int i = 0; i < removeAtIndices.count; i++) {
removedViewsWithIndices[i] = @[ removeAtIndices[i], permanentlyRemovedChildren[i] ];
}
[removedViewsWithIndices
sortUsingComparator:^NSComparisonResult(NSArray<id> *_Nonnull obj1, NSArray<id> *_Nonnull obj2) {
return [(NSNumber *)obj1[0] compare:(NSNumber *)obj2[0]];
}];
[originalSelf.animationsManager reattachAnimatedChildren:permanentlyRemovedChildren
toContainer:container
atIndices:removeAtIndices];
}
- (RCTViewManagerUIBlock)reanimated_uiBlockWithLayoutUpdateForRootView:(RCTRootShadowView *)rootShadowView
{
if (!reanimated::FeaturesConfig::isLayoutAnimationEnabled()) {
return [self reanimated_uiBlockWithLayoutUpdateForRootView:rootShadowView];
}
RCTUIManager *originalSelf = (RCTUIManager *)self;
#if REACT_NATIVE_MINOR_VERSION >= 73
NSPointerArray *affectedShadowViews = [NSPointerArray weakObjectsPointerArray];
#else
NSHashTable<RCTShadowView *> *affectedShadowViews = [NSHashTable weakObjectsHashTable];
#endif
[rootShadowView layoutWithAffectedShadowViews:affectedShadowViews];
if (!affectedShadowViews.count) {
// no frame change results in no UI update block
return nil;
}
typedef struct {
CGRect frame;
UIUserInterfaceLayoutDirection layoutDirection;
BOOL isNew;
BOOL parentIsNew;
RCTDisplayType displayType;
} RCTFrameData;
// Construct arrays then hand off to main thread
NSUInteger count = affectedShadowViews.count;
NSMutableArray *reactTags = [[NSMutableArray alloc] initWithCapacity:count];
NSMutableData *framesData = [[NSMutableData alloc] initWithLength:sizeof(RCTFrameData) * count];
{
NSUInteger index = 0;
RCTFrameData *frameDataArray = (RCTFrameData *)framesData.mutableBytes;
for (RCTShadowView *shadowView in affectedShadowViews) {
reactTags[index] = shadowView.reactTag;
RCTLayoutMetrics layoutMetrics = shadowView.layoutMetrics;
frameDataArray[index++] = (RCTFrameData){
layoutMetrics.frame,
layoutMetrics.layoutDirection,
shadowView.isNewView,
shadowView.superview.isNewView,
layoutMetrics.displayType};
}
}
for (RCTShadowView *shadowView in affectedShadowViews) {
// We have to do this after we build the parentsAreNew array.
shadowView.newView = NO;
NSNumber *reactTag = shadowView.reactTag;
if (shadowView.onLayout) {
CGRect frame = shadowView.layoutMetrics.frame;
shadowView.onLayout(@{
@"layout" : @{
@"x" : @(frame.origin.x),
@"y" : @(frame.origin.y),
@"width" : @(frame.size.width),
@"height" : @(frame.size.height),
},
});
}
if (RCTIsReactRootView(reactTag) && [shadowView isKindOfClass:[RCTRootShadowView class]]) {
CGSize contentSize = shadowView.layoutMetrics.frame.size;
RCTExecuteOnMainQueue(^{
REAUIView *view = [originalSelf viewForReactTag:(NSNumber *)reactTag];
RCTAssert(view != nil, @"view (for ID %@) not found", reactTag);
RCTRootView *rootView = (RCTRootView *)[view superview];
if ([rootView isKindOfClass:[RCTRootView class]]) {
rootView.intrinsicContentSize = contentSize;
}
});
}
}
// Perform layout (possibly animated)
return ^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, REAUIView *> *viewRegistry) {
const RCTFrameData *frameDataArray = (const RCTFrameData *)framesData.bytes;
RCTLayoutAnimationGroup *layoutAnimationGroup = [uiManager valueForKey:@"_layoutAnimationGroup"];
__block NSUInteger completionsCalled = 0;
NSMutableDictionary<NSNumber *, REASnapshot *> *snapshotsBefore = [NSMutableDictionary dictionary];
NSInteger index = 0;
for (NSNumber *reactTag in reactTags) {
RCTFrameData frameData = frameDataArray[index++];
REAUIView *view = [originalSelf viewForReactTag:(NSNumber *)reactTag];
CGRect frame = frameData.frame;
UIUserInterfaceLayoutDirection layoutDirection = frameData.layoutDirection;
BOOL isNew = frameData.isNew;
RCTLayoutAnimation *updatingLayoutAnimation = isNew ? nil : layoutAnimationGroup.updatingLayoutAnimation;
BOOL shouldAnimateCreation = isNew && !frameData.parentIsNew;
RCTLayoutAnimation *creatingLayoutAnimation =
shouldAnimateCreation ? layoutAnimationGroup.creatingLayoutAnimation : nil;
BOOL isHidden = frameData.displayType == RCTDisplayTypeNone;
void (^completion)(BOOL) = ^(BOOL finished) {
completionsCalled++;
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.
layoutAnimationGroup.callback = nil;
}
};
if (view.reactLayoutDirection != layoutDirection) {
view.reactLayoutDirection = layoutDirection;
}
if (view.isHidden != isHidden) {
view.hidden = isHidden;
}
// Reanimated changes /start
REASnapshot *snapshotBefore =
isNew ? nil : [originalSelf.animationsManager prepareSnapshotBeforeMountForView:view];
snapshotsBefore[reactTag] = snapshotBefore;
// Reanimated changes /end
if (creatingLayoutAnimation) {
// Animate view creation
[view reactSetFrame:frame];
CATransform3D finalTransform = view.layer.transform;
CGFloat finalOpacity = view.layer.opacity;
NSString *property = creatingLayoutAnimation.property;
if ([property isEqualToString:@"scaleXY"]) {
view.layer.transform = CATransform3DMakeScale(0, 0, 0);
} else if ([property isEqualToString:@"scaleX"]) {
view.layer.transform = CATransform3DMakeScale(0, 1, 0);
} else if ([property isEqualToString:@"scaleY"]) {
view.layer.transform = CATransform3DMakeScale(1, 0, 0);
} else if ([property isEqualToString:@"opacity"]) {
view.layer.opacity = 0.0;
} else {
RCTLogError(@"Unsupported layout animation createConfig property %@", creatingLayoutAnimation.property);
}
[creatingLayoutAnimation
performAnimations:^{
if ([property isEqualToString:@"scaleX"] || [property isEqualToString:@"scaleY"] ||
[property isEqualToString:@"scaleXY"]) {
view.layer.transform = finalTransform;
} else if ([property isEqualToString:@"opacity"]) {
view.layer.opacity = finalOpacity;
}
}
withCompletionBlock:completion];
} else if (updatingLayoutAnimation) {
// Animate view update
[updatingLayoutAnimation
performAnimations:^{
[view reactSetFrame:frame];
}
withCompletionBlock:completion];
} else {
// Update without animation
[view reactSetFrame:frame];
completion(YES);
}
}
// Reanimated changes /start
index = 0;
for (NSNumber *reactTag in reactTags) {
RCTFrameData frameData = frameDataArray[index++];
REAUIView *view = [originalSelf viewForReactTag:(NSNumber *)reactTag];
BOOL isNew = frameData.isNew;
CGRect frame = frameData.frame;
REASnapshot *snapshotBefore = snapshotsBefore[reactTag];
if (isNew || snapshotBefore != nil) {
[originalSelf.animationsManager viewDidMount:view withBeforeSnapshot:snapshotBefore withNewFrame:frame];
}
}
// Clean up
// below line serves as this one uiManager->_layoutAnimationGroup = nil;, because we don't have access to the
// private field
[uiManager setValue:nil forKey:@"_layoutAnimationGroup"];
[originalSelf.animationsManager viewsDidLayout];
// Reanimated changes /end
};
}
- (void)reanimated_addUIBlock:(RCTViewManagerUIBlock)block
{
RCTAssertUIManagerQueue();
hasPendingBlocks = true;
[self reanimated_addUIBlock:block];
}
- (void)reanimated_prependUIBlock:(RCTViewManagerUIBlock)block
{
RCTAssertUIManagerQueue();
hasPendingBlocks = true;
[self reanimated_prependUIBlock:block];
}
- (void)reanimated_flushUIBlocksWithCompletion:(void (^)(void))completion
{
RCTAssertUIManagerQueue();
if (hasPendingBlocks) {
++isFlushingBlocks;
hasPendingBlocks = false;
[self reanimated_addUIBlock:^(
__unused RCTUIManager *manager, __unused NSDictionary<NSNumber *, REAUIView *> *viewRegistry) {
--isFlushingBlocks;
}];
}
[self reanimated_flushUIBlocksWithCompletion:completion];
}
@end

View File

@@ -0,0 +1,16 @@
#ifndef RCT_NEW_ARCH_ENABLED
#import <Foundation/Foundation.h>
#import <React/RCTEventDispatcher.h>
NS_ASSUME_NONNULL_BEGIN
@interface RCTEventDispatcher (Reanimated)
- (void)reanimated_sendEvent:(id<RCTEvent>)event;
@end
NS_ASSUME_NONNULL_END
#endif // RCT_NEW_ARCH_ENABLED

View File

@@ -0,0 +1,42 @@
#ifndef RCT_NEW_ARCH_ENABLED
#import <RNReanimated/RCTEventDispatcher+Reanimated.h>
#import <RNReanimated/REAModule.h>
#import <React/RCTBridge+Private.h>
#import <React/RCTEventDispatcher.h>
#import <objc/message.h>
@implementation RCTEventDispatcher (Reanimated)
- (void)reanimated_sendEvent:(id<RCTEvent>)event
{
// Pass the event to Reanimated
static __weak RCTBridge *bridge;
static __weak REAModule *reaModule;
if (bridge != self.bridge) {
bridge = self.bridge;
reaModule = nil;
}
if (reaModule == nil) {
reaModule = [bridge moduleForName:@"ReanimatedModule"];
}
[reaModule eventDispatcherWillDispatchEvent:event];
// Pass the event to React Native by calling the original method
[self reanimated_sendEvent:event];
}
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
Method originalMethod = class_getInstanceMethod(class, @selector(sendEvent:));
Method swizzledMethod = class_getInstanceMethod(class, @selector(reanimated_sendEvent:));
method_exchangeImplementations(originalMethod, swizzledMethod);
});
}
@end
#endif // RCT_NEW_ARCH_ENABLED

View File

@@ -0,0 +1,11 @@
#if TARGET_OS_OSX
#import <React/RCTUIKit.h>
@interface RCTUIView (Reanimated)
@property CGPoint center;
@end
#endif

View File

@@ -0,0 +1,26 @@
#if TARGET_OS_OSX
#import <RNReanimated/RCTUIView+Reanimated.h>
#import <React/RCTUIKit.h>
@implementation RCTUIView (Reanimated)
- (CGPoint)center
{
NSRect frameRect = self.frame;
CGFloat xCenter = frameRect.origin.x + frameRect.size.width / 2;
CGFloat yCenter = frameRect.origin.y + frameRect.size.height / 2;
return CGPointMake(xCenter, yCenter);
}
- (void)setCenter:(CGPoint)point
{
NSRect frameRect = self.frame;
CGFloat xOrigin = point.x - frameRect.size.width / 2;
CGFloat yOrigin = point.y - frameRect.size.height / 2;
self.frame = CGRectMake(xOrigin, yOrigin, frameRect.size.width, frameRect.size.height);
}
@end
#endif

View File

@@ -0,0 +1,21 @@
#if !TARGET_OS_OSX
#import <QuartzCore/CADisplayLink.h>
typedef CADisplayLink READisplayLink;
#else // TARGET_OS_OSX [
#ifdef __cplusplus
extern "C" {
#endif
#import <React/RCTPlatformDisplayLink.h>
#ifdef __cplusplus
}
#endif
typedef RCTPlatformDisplayLink READisplayLink;
#endif // ] TARGET_OS_OSX

View File

@@ -0,0 +1,40 @@
#ifdef RCT_NEW_ARCH_ENABLED
#import <React/RCTInitializing.h>
#if REACT_NATIVE_MINOR_VERSION >= 74
#import <React/RCTRuntimeExecutorModule.h>
#import <ReactCommon/RCTRuntimeExecutor.h>
#endif // REACT_NATIVE_MINOR_VERSION >= 74
#import <rnreanimated/rnreanimated.h>
#else // RCT_NEW_ARCH_ENABLED
#import <React/RCTBridgeModule.h>
#endif // RCT_NEW_ARCH_ENABLED
#import <React/RCTEventDispatcher.h>
#import <React/RCTEventEmitter.h>
#import <React/RCTUIManager.h>
#import <React/RCTUIManagerObserverCoordinator.h>
#import <React/RCTUIManagerUtils.h>
#import <RNReanimated/REAAnimationsManager.h>
#import <RNReanimated/REANodesManager.h>
@interface REAModule : RCTEventEmitter
#ifdef RCT_NEW_ARCH_ENABLED
<NativeReanimatedModuleSpec,
RCTInitializing,
#if REACT_NATIVE_MINOR_VERSION >= 74
RCTRuntimeExecutorModule,
#endif // REACT_NATIVE_MINOR_VERSION >= 74
#else
<RCTBridgeModule,
#endif // RCT_NEW_ARCH_ENABLED
RCTEventDispatcherObserver,
RCTUIManagerObserver>
@property (nonatomic, readonly) REANodesManager *nodesManager;
@property REAAnimationsManager *animationsManager;
#ifdef RCT_NEW_ARCH_ENABLED
- (void)installReanimatedAfterReload;
#endif // RCT_NEW_ARCH_ENABLED
@end

View File

@@ -0,0 +1,344 @@
#import <React/RCTBridge+Private.h>
#ifdef RCT_NEW_ARCH_ENABLED
#import <React/RCTFabricSurface.h>
#import <React/RCTScheduler.h>
#import <React/RCTSurface.h>
#import <React/RCTSurfacePresenter.h>
#import <React/RCTSurfacePresenterBridgeAdapter.h>
#import <React/RCTSurfaceView.h>
#if REACT_NATIVE_MINOR_VERSION < 73
#import <React/RCTRuntimeExecutorFromBridge.h>
#endif // REACT_NATIVE_MINOR_VERSION < 73
#endif // RCT_NEW_ARCH_ENABLED
#ifdef RCT_NEW_ARCH_ENABLED
#import <RNReanimated/REAInitializerRCTFabricSurface.h>
#endif // RCT_NEW_ARCH_ENABLED
#import <RNReanimated/NativeProxy.h>
#import <RNReanimated/REAModule.h>
#import <RNReanimated/REANodesManager.h>
#import <RNReanimated/REAUIKit.h>
#import <RNReanimated/RNRuntimeDecorator.h>
#import <RNReanimated/ReanimatedJSIUtils.h>
#import <RNReanimated/SingleInstanceChecker.h>
#import <RNReanimated/WorkletRuntime.h>
#import <RNReanimated/WorkletRuntimeCollector.h>
#if __has_include(<UIKit/UIAccessibility.h>)
#import <UIKit/UIAccessibility.h>
#endif // __has_include(<UIKit/UIAccessibility.h>)
using namespace facebook::react;
using namespace reanimated;
@interface RCTBridge (JSIRuntime)
- (void *)runtime;
@end
@interface RCTBridge (RCTTurboModule)
- (std::shared_ptr<facebook::react::CallInvoker>)jsCallInvoker;
- (void)_tryAndHandleError:(dispatch_block_t)block;
@end
#ifdef RCT_NEW_ARCH_ENABLED
static __strong REAInitializerRCTFabricSurface *reaSurface;
#else
typedef void (^AnimatedOperation)(REANodesManager *nodesManager);
#endif // RCT_NEW_ARCH_ENABLED
@implementation REAModule {
#ifdef RCT_NEW_ARCH_ENABLED
__weak RCTSurfacePresenter *_surfacePresenter;
std::weak_ptr<NativeReanimatedModule> weakNativeReanimatedModule_;
#else
NSMutableArray<AnimatedOperation> *_operations;
#endif // RCT_NEW_ARCH_ENABLED
#ifndef NDEBUG
SingleInstanceChecker<REAModule> singleInstanceChecker_;
#endif // NDEBUG
bool hasListeners;
bool _isBridgeless;
}
@synthesize moduleRegistry = _moduleRegistry;
#if REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
@synthesize runtimeExecutor = _runtimeExecutor;
#endif // REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
RCT_EXPORT_MODULE(ReanimatedModule);
#ifdef RCT_NEW_ARCH_ENABLED
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
#endif // RCT_NEW_ARCH_ENABLED
- (void)invalidate
{
#ifdef RCT_NEW_ARCH_ENABLED
[[NSNotificationCenter defaultCenter] removeObserver:self];
#endif // RCT_NEW_ARCH_ENABLED
[_nodesManager invalidate];
[super invalidate];
}
- (dispatch_queue_t)methodQueue
{
// This module needs to be on the same queue as the UIManager to avoid
// having to lock `_operations` and `_preOperations` since `uiManagerWillPerformMounting`
// will be called from that queue.
return RCTGetUIManagerQueue();
}
#ifdef RCT_NEW_ARCH_ENABLED
- (std::shared_ptr<UIManager>)getUIManager
{
RCTScheduler *scheduler = [_surfacePresenter scheduler];
return scheduler.uiManager;
}
- (void)injectDependencies:(jsi::Runtime &)runtime
{
const auto &uiManager = [self getUIManager];
react_native_assert(uiManager.get() != nil);
if (auto nativeReanimatedModule = weakNativeReanimatedModule_.lock()) {
nativeReanimatedModule->initializeFabric(uiManager);
}
}
#pragma mark-- Initialize
- (void)installReanimatedAfterReload
{
// called from REAInitializerRCTFabricSurface::start
__weak __typeof__(self) weakSelf = self;
_surfacePresenter = self.bridge.surfacePresenter;
[_nodesManager setSurfacePresenter:_surfacePresenter];
// to avoid deadlock we can't use Executor from React Native
// but we can create own and use it because initialization is already synchronized
react_native_assert(self.bridge != nil);
RCTRuntimeExecutorFromBridge(self.bridge)(^(jsi::Runtime &runtime) {
if (__typeof__(self) strongSelf = weakSelf) {
[strongSelf injectDependencies:runtime];
}
});
}
- (void)handleJavaScriptDidLoadNotification:(NSNotification *)notification
{
[self attachReactEventListener];
}
- (void)attachReactEventListener
{
RCTScheduler *scheduler = [_surfacePresenter scheduler];
__weak __typeof__(self) weakSelf = self;
_surfacePresenter.runtimeExecutor(^(jsi::Runtime &runtime) {
__typeof__(self) strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
if (auto nativeReanimatedModule = strongSelf->weakNativeReanimatedModule_.lock()) {
auto eventListener =
std::make_shared<facebook::react::EventListener>([nativeReanimatedModule](const RawEvent &rawEvent) {
if (!RCTIsMainQueue()) {
// event listener called on the JS thread, let's ignore this event
// as we cannot safely access worklet runtime here
// and also we don't care about topLayout events
return false;
}
return nativeReanimatedModule->handleRawEvent(rawEvent, CACurrentMediaTime() * 1000);
});
[scheduler addEventListener:eventListener];
}
});
}
#pragma mark-- Bridgeless methods
/*
* Taken from RCTNativeAnimatedTurboModule:
* This selector is invoked via BridgelessTurboModuleSetup.
*/
- (void)setSurfacePresenter:(id<RCTSurfacePresenterStub>)surfacePresenter
{
_surfacePresenter = surfacePresenter;
_isBridgeless = true;
}
- (void)setBridge:(RCTBridge *)bridge
{
[super setBridge:bridge];
// only within the first loading `self.bridge.surfacePresenter` exists
// during the reload `self.bridge.surfacePresenter` is null
if (self.bridge.surfacePresenter) {
_surfacePresenter = self.bridge.surfacePresenter;
}
[self setReaSurfacePresenter];
_nodesManager = [[REANodesManager alloc] initWithModule:self bridge:bridge surfacePresenter:_surfacePresenter];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleJavaScriptDidLoadNotification:)
name:RCTJavaScriptDidLoadNotification
object:nil];
[[self.moduleRegistry moduleForName:"EventDispatcher"] addDispatchObserver:self];
}
- (void)setReaSurfacePresenter
{
if (reaSurface == nil) {
// we need only one instance because SurfacePresenter is the same during the application lifetime
reaSurface = [[REAInitializerRCTFabricSurface alloc] init];
[_surfacePresenter registerSurface:reaSurface];
}
reaSurface.reaModule = self;
}
#else // RCT_NEW_ARCH_ENABLED
- (void)setBridge:(RCTBridge *)bridge
{
[super setBridge:bridge];
_nodesManager = [[REANodesManager alloc] initWithModule:self uiManager:self.bridge.uiManager];
_operations = [NSMutableArray new];
[bridge.uiManager.observerCoordinator addObserver:self];
_animationsManager = [[REAAnimationsManager alloc] initWithUIManager:bridge.uiManager];
}
#pragma mark-- Batch handling
- (void)addOperationBlock:(AnimatedOperation)operation
{
[_operations addObject:operation];
}
#pragma mark - RCTUIManagerObserver
- (void)uiManagerWillPerformMounting:(RCTUIManager *)uiManager
{
[_nodesManager maybeFlushUpdateBuffer];
if (_operations.count == 0) {
return;
}
NSArray<AnimatedOperation> *operations = _operations;
_operations = [NSMutableArray new];
REANodesManager *nodesManager = _nodesManager;
[uiManager
addUIBlock:^(__unused RCTUIManager *manager, __unused NSDictionary<NSNumber *, REAUIView *> *viewRegistry) {
for (AnimatedOperation operation in operations) {
operation(nodesManager);
}
[nodesManager operationsBatchDidComplete];
}];
}
#endif // RCT_NEW_ARCH_ENABLED
#pragma mark-- Events
- (NSArray<NSString *> *)supportedEvents
{
return @[ @"onReanimatedCall", @"onReanimatedPropsChange" ];
}
- (void)eventDispatcherWillDispatchEvent:(id<RCTEvent>)event
{
// Events can be dispatched from any queue
[_nodesManager dispatchEvent:event];
}
- (void)startObserving
{
hasListeners = YES;
}
- (void)stopObserving
{
hasListeners = NO;
}
- (void)sendEventWithName:(NSString *)eventName body:(id)body
{
if (hasListeners) {
[super sendEventWithName:eventName body:body];
}
}
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(installTurboModule : (nonnull NSString *)valueUnpackerCode)
{
if (_isBridgeless) {
#if REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
RCTCxxBridge *cxxBridge = (RCTCxxBridge *)self.bridge;
auto &rnRuntime = *(jsi::Runtime *)cxxBridge.runtime;
auto executorFunction = ([executor = _runtimeExecutor](std::function<void(jsi::Runtime & runtime)> &&callback) {
// Convert to Objective-C block so it can be captured properly.
__block auto callbackBlock = callback;
[executor execute:^(jsi::Runtime &runtime) {
callbackBlock(runtime);
}];
});
auto nativeReanimatedModule = reanimated::createReanimatedModuleBridgeless(
_moduleRegistry, rnRuntime, std::string([valueUnpackerCode UTF8String]), executorFunction);
[self attachReactEventListener];
[self commonInit:nativeReanimatedModule withRnRuntime:rnRuntime];
#else // REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
[NSException raise:@"Missing bridge" format:@"[Reanimated] Failed to obtain the bridge."];
#endif // REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
} else {
facebook::jsi::Runtime *jsiRuntime = [self.bridge respondsToSelector:@selector(runtime)]
? reinterpret_cast<facebook::jsi::Runtime *>(self.bridge.runtime)
: nullptr;
if (jsiRuntime) {
auto nativeReanimatedModule = reanimated::createReanimatedModule(
self.bridge, self.bridge.jsCallInvoker, std::string([valueUnpackerCode UTF8String]));
jsi::Runtime &rnRuntime = *jsiRuntime;
[self commonInit:nativeReanimatedModule withRnRuntime:rnRuntime];
}
}
return @YES;
}
#ifdef RCT_NEW_ARCH_ENABLED
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeReanimatedModuleSpecJSI>(params);
}
#endif // RCT_NEW_ARCH_ENABLED
- (void)commonInit:(std::shared_ptr<NativeReanimatedModule>)nativeReanimatedModule
withRnRuntime:(jsi::Runtime &)rnRuntime
{
#if __has_include(<UIKit/UIAccessibility.h>)
auto isReducedMotion = UIAccessibilityIsReduceMotionEnabled();
#else
auto isReducedMotion = NSWorkspace.sharedWorkspace.accessibilityDisplayShouldReduceMotion;
#endif
WorkletRuntimeCollector::install(rnRuntime);
RNRuntimeDecorator::decorate(rnRuntime, nativeReanimatedModule, isReducedMotion);
#ifdef RCT_NEW_ARCH_ENABLED
weakNativeReanimatedModule_ = nativeReanimatedModule;
if (self->_surfacePresenter != nil) {
// reload, uiManager is null right now, we need to wait for `installReanimatedAfterReload`
[self injectDependencies:rnRuntime];
}
#endif // RCT_NEW_ARCH_ENABLED
}
@end

View File

@@ -0,0 +1,63 @@
#import <React/RCTBridgeModule.h>
#import <React/RCTUIManager.h>
#ifdef RCT_NEW_ARCH_ENABLED
#import <React/RCTSurfacePresenterStub.h>
#endif // RCT_NEW_ARCH_ENABLED
#import <RNReanimated/READisplayLink.h>
@class REAModule;
typedef void (^REAOnAnimationCallback)(READisplayLink *displayLink);
typedef void (^REANativeAnimationOp)(RCTUIManager *uiManager);
typedef void (^REAEventHandler)(id<RCTEvent> event);
typedef void (^CADisplayLinkOperation)(READisplayLink *displayLink);
#ifdef RCT_NEW_ARCH_ENABLED
typedef void (^REAPerformOperations)();
#endif
@interface REANodesManager : NSObject
@property (nonatomic, weak, nullable) RCTUIManager *uiManager;
@property (nonatomic, weak, nullable) REAModule *reanimatedModule;
@property (nonatomic, readonly) CFTimeInterval currentAnimationTimestamp;
@property (nonatomic, nullable) NSSet<NSString *> *uiProps;
@property (nonatomic, nullable) NSSet<NSString *> *nativeProps;
#ifdef RCT_NEW_ARCH_ENABLED
- (nonnull instancetype)initWithModule:(REAModule *)reanimatedModule
bridge:(RCTBridge *)bridge
surfacePresenter:(id<RCTSurfacePresenterStub>)surfacePresenter;
#else
- (instancetype)initWithModule:(REAModule *)reanimatedModule uiManager:(RCTUIManager *)uiManager;
#endif // RCT_NEW_ARCH_ENABLED
- (void)invalidate;
- (void)operationsBatchDidComplete;
- (void)postOnAnimation:(REAOnAnimationCallback)clb;
- (void)registerEventHandler:(REAEventHandler)eventHandler;
- (void)dispatchEvent:(id<RCTEvent>)event;
#ifdef RCT_NEW_ARCH_ENABLED
- (void)setSurfacePresenter:(id<RCTSurfacePresenterStub>)surfacePresenter;
- (void)registerPerformOperations:(REAPerformOperations)performOperations;
- (void)synchronouslyUpdateViewOnUIThread:(nonnull NSNumber *)viewTag props:(nonnull NSDictionary *)uiProps;
#else
- (void)configureUiProps:(nonnull NSSet<NSString *> *)uiPropsSet
andNativeProps:(nonnull NSSet<NSString *> *)nativePropsSet;
- (void)updateProps:(nonnull NSDictionary *)props
ofViewWithTag:(nonnull NSNumber *)viewTag
withName:(nonnull NSString *)viewName;
- (void)maybeFlushUpdateBuffer;
- (void)enqueueUpdateViewOnNativeThread:(nonnull NSNumber *)reactTag
viewName:(NSString *)viewName
nativeProps:(NSMutableDictionary *)nativeProps
trySynchronously:(BOOL)trySync;
- (NSString *)obtainProp:(nonnull NSNumber *)viewTag propName:(nonnull NSString *)propName;
#endif // RCT_NEW_ARCH_ENABLED
- (void)maybeFlushUIUpdatesQueue;
@end

View File

@@ -0,0 +1,606 @@
#import <RNReanimated/READisplayLink.h>
#import <RNReanimated/REAModule.h>
#import <RNReanimated/REANodesManager.h>
#import <RNReanimated/REAUIKit.h>
#import <React/RCTConvert.h>
#ifdef RCT_NEW_ARCH_ENABLED
#import <React/RCTComponentViewRegistry.h>
#import <React/RCTMountingManager.h>
#import <React/RCTSurfacePresenter.h>
#import <react/renderer/core/ShadowNode.h>
#import <react/renderer/uimanager/UIManager.h>
#else
#import <stdatomic.h>
#endif // RCT_NEW_ARCH_ENABLED
#if __has_include(<RNScreens/RNSScreenStackHeaderConfig.h>)
#import <RNScreens/RNSScreenStackHeaderConfig.h>
#endif // __has_include(<RNScreens/RNSScreenStackHeaderConfig.h>)
#ifdef RCT_NEW_ARCH_ENABLED
using namespace facebook::react;
#endif // RCT_NEW_ARCH_ENABLED
// Interface below has been added in order to use private methods of RCTUIManager,
// RCTUIManager#UpdateView is a React Method which is exported to JS but in
// Objective-C it stays private
// RCTUIManager#setNeedsLayout is a method which updated layout only which
// in its turn will trigger relayout if no batch has been activated
@interface RCTUIManager ()
- (void)updateView:(nonnull NSNumber *)reactTag viewName:(NSString *)viewName props:(NSDictionary *)props;
- (void)setNeedsLayout;
- (bool)isExecutingUpdatesBatch;
@end
@interface RCTUIManager (SyncUpdates)
- (void)runSyncUIUpdatesWithObserver:(id<RCTUIManagerObserver>)observer;
@end
@interface ComponentUpdate : NSObject
@property (nonnull) NSMutableDictionary *props;
@property (nonnull) NSNumber *viewTag;
@property (nonnull) NSString *viewName;
@end
@implementation ComponentUpdate
@end
@implementation RCTUIManager (SyncUpdates)
- (void)runSyncUIUpdatesWithObserver:(id<RCTUIManagerObserver>)observer
{
// before we run uimanager batch complete, we override coordinator observers list
// to avoid observers from firing. This is done because we only want the uimanager
// related operations to run and not all other operations (including the ones enqueued
// by reanimated or native animated modules) from being scheduled. If we were to allow
// other modules to execute some logic from this sync uimanager run there is a possibility
// that the commands will execute out of order or that we intercept a batch of commands that
// those modules may be in a middle of (we verify that batch isn't in progress for uimodule
// but can't do the same for all remaining modules)
// store reference to the observers array
id oldObservers = [self.observerCoordinator valueForKey:@"_observers"];
// temporarily replace observers with a table containing just nodesManager (we need
// this to capture mounting block)
NSHashTable<id<RCTUIManagerObserver>> *soleObserver = [NSHashTable new];
[soleObserver addObject:observer];
[self.observerCoordinator setValue:soleObserver forKey:@"_observers"];
// run batch
[self batchDidComplete];
// restore old observers table
[self.observerCoordinator setValue:oldObservers forKey:@"_observers"];
}
@end
#ifndef RCT_NEW_ARCH_ENABLED
@interface REASyncUpdateObserver : NSObject <RCTUIManagerObserver>
@end
@implementation REASyncUpdateObserver {
volatile void (^_mounting)(void);
volatile BOOL _waitTimedOut;
dispatch_semaphore_t _semaphore;
}
- (instancetype)init
{
self = [super init];
if (self) {
_mounting = nil;
_waitTimedOut = NO;
_semaphore = dispatch_semaphore_create(0);
}
return self;
}
- (void)dealloc
{
RCTAssert(_mounting == nil, @"Mounting block was set but never executed. This may lead to UI inconsistencies");
}
- (void)unblockUIThread
{
RCTAssertUIManagerQueue();
dispatch_semaphore_signal(_semaphore);
}
- (void)waitAndMountWithTimeout:(NSTimeInterval)timeout
{
RCTAssertMainQueue();
long result = dispatch_semaphore_wait(_semaphore, dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC));
if (result != 0) {
@synchronized(self) {
_waitTimedOut = YES;
}
}
if (_mounting) {
_mounting();
_mounting = nil;
}
}
- (BOOL)uiManager:(RCTUIManager *)manager performMountingWithBlock:(RCTUIManagerMountingBlock)block
{
RCTAssertUIManagerQueue();
@synchronized(self) {
if (_waitTimedOut) {
return NO;
} else {
_mounting = block;
return YES;
}
}
}
@end
#endif // RCT_NEW_ARCH_ENABLED
@implementation REANodesManager {
READisplayLink *_displayLink;
BOOL _wantRunUpdates;
NSMutableArray<REAOnAnimationCallback> *_onAnimationCallbacks;
BOOL _tryRunBatchUpdatesSynchronously;
REAEventHandler _eventHandler;
NSMutableDictionary<NSNumber *, ComponentUpdate *> *_componentUpdateBuffer;
NSMutableDictionary<NSNumber *, REAUIView *> *_viewRegistry;
#ifdef RCT_NEW_ARCH_ENABLED
__weak RCTBridge *_bridge;
REAPerformOperations _performOperations;
__weak id<RCTSurfacePresenterStub> _surfacePresenter;
NSMutableDictionary<NSNumber *, NSMutableDictionary *> *_operationsInBatch;
#else
NSMutableArray<REANativeAnimationOp> *_operationsInBatch;
volatile atomic_bool _shouldFlushUpdateBuffer;
#endif // RCT_NEW_ARCH_ENABLED
}
- (READisplayLink *)getDisplayLink
{
RCTAssertMainQueue();
if (!_displayLink) {
_displayLink = [READisplayLink displayLinkWithTarget:self selector:@selector(onAnimationFrame:)];
#if !TARGET_OS_OSX
_displayLink.preferredFramesPerSecond = 120; // will fallback to 60 fps for devices without Pro Motion display
#endif // TARGET_OS_OSX
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
return _displayLink;
}
- (void)useDisplayLinkOnMainQueue:(CADisplayLinkOperation)displayLinkOperation
{
__weak __typeof__(self) weakSelf = self;
RCTExecuteOnMainQueue(^{
__typeof__(self) strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
displayLinkOperation([strongSelf getDisplayLink]);
});
}
#ifdef RCT_NEW_ARCH_ENABLED
- (nonnull instancetype)initWithModule:(REAModule *)reanimatedModule
bridge:(RCTBridge *)bridge
surfacePresenter:(id<RCTSurfacePresenterStub>)surfacePresenter
{
if ((self = [super init])) {
_bridge = bridge;
_surfacePresenter = surfacePresenter;
_reanimatedModule = reanimatedModule;
_wantRunUpdates = NO;
_onAnimationCallbacks = [NSMutableArray new];
_operationsInBatch = [NSMutableDictionary new];
_componentUpdateBuffer = [NSMutableDictionary new];
_viewRegistry = [_uiManager valueForKey:@"_viewRegistry"];
_eventHandler = ^(id<RCTEvent> event) {
// no-op
};
}
#else
- (instancetype)initWithModule:(REAModule *)reanimatedModule uiManager:(RCTUIManager *)uiManager
{
if ((self = [super init])) {
_reanimatedModule = reanimatedModule;
_uiManager = uiManager;
_wantRunUpdates = NO;
_onAnimationCallbacks = [NSMutableArray new];
_operationsInBatch = [NSMutableArray new];
_componentUpdateBuffer = [NSMutableDictionary new];
_viewRegistry = [_uiManager valueForKey:@"_viewRegistry"];
_shouldFlushUpdateBuffer = false;
}
#endif // RCT_NEW_ARCH_ENABLED
[self useDisplayLinkOnMainQueue:^(READisplayLink *displayLink) {
[displayLink setPaused:YES];
}];
return self;
}
- (void)invalidate
{
_eventHandler = nil;
[self useDisplayLinkOnMainQueue:^(READisplayLink *displayLink) {
[displayLink invalidate];
}];
}
#ifdef RCT_NEW_ARCH_ENABLED
- (void)setSurfacePresenter:(id<RCTSurfacePresenterStub>)surfacePresenter
{
_surfacePresenter = surfacePresenter;
}
#endif // RCT_NEW_ARCH_ENABLED
- (void)operationsBatchDidComplete
{
if (![[self getDisplayLink] isPaused]) {
// if display link is set it means some of the operations that have run as a part of the batch
// requested updates. We want updates to be run in the same frame as in which operations have
// been scheduled as it may mean the new view has just been mounted and expects its initial
// props to be calculated.
// Unfortunately if the operation has just scheduled animation callback it won't run until the
// next frame, so it's being triggered manually.
_wantRunUpdates = YES;
[self performOperations];
}
}
- (void)postOnAnimation:(REAOnAnimationCallback)clb
{
[_onAnimationCallbacks addObject:clb];
[self startUpdatingOnAnimationFrame];
}
- (void)registerEventHandler:(REAEventHandler)eventHandler
{
_eventHandler = eventHandler;
}
#ifdef RCT_NEW_ARCH_ENABLED
- (void)registerPerformOperations:(REAPerformOperations)performOperations
{
_performOperations = performOperations;
}
#endif // RCT_NEW_ARCH_ENABLED
- (void)startUpdatingOnAnimationFrame
{
[[self getDisplayLink] setPaused:NO];
}
- (void)stopUpdatingOnAnimationFrame
{
[[self getDisplayLink] setPaused:YES];
}
- (void)onAnimationFrame:(READisplayLink *)displayLink
{
NSArray<REAOnAnimationCallback> *callbacks = _onAnimationCallbacks;
_onAnimationCallbacks = [NSMutableArray new];
// When one of the callbacks would postOnAnimation callback we don't want
// to process it until the next frame. This is why we cpy the array before
// we iterate over it
for (REAOnAnimationCallback block in callbacks) {
block(displayLink);
}
[self performOperations];
if (_onAnimationCallbacks.count == 0) {
[self stopUpdatingOnAnimationFrame];
}
}
- (void)performOperations
{
#ifdef RCT_NEW_ARCH_ENABLED
_performOperations(); // calls NativeReanimatedModule::performOperations
_wantRunUpdates = NO;
#else
if (_operationsInBatch.count != 0) {
NSMutableArray<REANativeAnimationOp> *copiedOperationsQueue = _operationsInBatch;
_operationsInBatch = [NSMutableArray new];
BOOL trySynchronously = _tryRunBatchUpdatesSynchronously;
_tryRunBatchUpdatesSynchronously = NO;
__weak __typeof__(self) weakSelf = self;
REASyncUpdateObserver *syncUpdateObserver = [REASyncUpdateObserver new];
RCTExecuteOnUIManagerQueue(^{
__typeof__(self) strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
// It is safe to call `isExecutingUpdatesBatch` in this context (Shadow thread) because both
// the Shadow thread and UI Thread are locked at this point. The UI Thread is specifically
// locked by the lock inside `REASyncUpdateObserver`, ensuring a safe reading operation.
BOOL canUpdateSynchronously = trySynchronously && ![strongSelf.uiManager isExecutingUpdatesBatch];
if (!canUpdateSynchronously) {
[syncUpdateObserver unblockUIThread];
}
for (int i = 0; i < copiedOperationsQueue.count; i++) {
copiedOperationsQueue[i](strongSelf.uiManager);
}
if (canUpdateSynchronously) {
[strongSelf.uiManager runSyncUIUpdatesWithObserver:syncUpdateObserver];
[syncUpdateObserver unblockUIThread];
}
// In case canUpdateSynchronously=true we still have to send uiManagerWillPerformMounting event
// to observers because some components (e.g. TextInput) update their UIViews only on that event.
[strongSelf.uiManager setNeedsLayout];
});
if (trySynchronously) {
// The 16ms timeout here aims to match the frame duration. It may make sense to read that parameter
// from CADisplayLink but it is easier to hardcode it for the time being.
// The reason why we use frame duration here is that if takes longer than one frame to complete layout tasks
// there is no point of synchronizing layout with the UI interaction as we get that one frame delay anyways.
[syncUpdateObserver waitAndMountWithTimeout:0.016];
}
}
_wantRunUpdates = NO;
#endif // RCT_NEW_ARCH_ENABLED
}
#ifdef RCT_NEW_ARCH_ENABLED
// nothing
#else
- (void)enqueueUpdateViewOnNativeThread:(nonnull NSNumber *)reactTag
viewName:(NSString *)viewName
nativeProps:(NSMutableDictionary *)nativeProps
trySynchronously:(BOOL)trySync
{
if (trySync) {
_tryRunBatchUpdatesSynchronously = YES;
}
[_operationsInBatch addObject:^(RCTUIManager *uiManager) {
[uiManager updateView:reactTag viewName:viewName props:nativeProps];
}];
}
#endif // RCT_NEW_ARCH_ENABLED
- (void)dispatchEvent:(id<RCTEvent>)event
{
__weak REAEventHandler eventHandler = _eventHandler;
__weak __typeof__(self) weakSelf = self;
RCTExecuteOnMainQueue(^void() {
__typeof__(self) strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
if (eventHandler == nil) {
return;
}
eventHandler(event);
[strongSelf performOperations];
});
}
#ifdef RCT_NEW_ARCH_ENABLED
// nothing
#else
- (void)configureUiProps:(nonnull NSSet<NSString *> *)uiPropsSet
andNativeProps:(nonnull NSSet<NSString *> *)nativePropsSet
{
_uiProps = uiPropsSet;
_nativeProps = nativePropsSet;
}
#endif // RCT_NEW_ARCH_ENABLED
- (BOOL)isNativeViewMounted:(NSNumber *)viewTag
{
REAUIView *view = [_uiManager viewForReactTag:(NSNumber *)viewTag];
if (view.superview != nil) {
return YES;
}
#if __has_include(<RNScreens/RNSScreenStackHeaderConfig.h>)
if ([view isKindOfClass:[RNSScreenStackHeaderConfig class]]) {
return ((RNSScreenStackHeaderConfig *)view).screenView != nil;
}
#endif // __has_include(<RNScreens/RNSScreenStackHeaderConfig.h>)
return NO;
}
#ifdef RCT_NEW_ARCH_ENABLED
- (void)synchronouslyUpdateViewOnUIThread:(nonnull NSNumber *)viewTag props:(nonnull NSDictionary *)uiProps
{
// adapted from RCTPropsAnimatedNode.m
RCTSurfacePresenter *surfacePresenter = _bridge.surfacePresenter ?: _surfacePresenter;
RCTComponentViewRegistry *componentViewRegistry = surfacePresenter.mountingManager.componentViewRegistry;
REAUIView<RCTComponentViewProtocol> *componentView =
[componentViewRegistry findComponentViewWithTag:[viewTag integerValue]];
NSSet<NSString *> *propKeysManagedByAnimated = [componentView propKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN];
[surfacePresenter synchronouslyUpdateViewOnUIThread:viewTag props:uiProps];
[componentView setPropKeysManagedByAnimated_DO_NOT_USE_THIS_IS_BROKEN:propKeysManagedByAnimated];
// `synchronouslyUpdateViewOnUIThread` does not flush props like `backgroundColor` etc.
// so that's why we need to call `finalizeUpdates` here.
[componentView finalizeUpdates:RNComponentViewUpdateMask{}];
}
#else
- (void)updateProps:(nonnull NSDictionary *)props
ofViewWithTag:(nonnull NSNumber *)viewTag
withName:(nonnull NSString *)viewName
{
ComponentUpdate *lastSnapshot = _componentUpdateBuffer[viewTag];
BOOL isNativeViewMounted = [self isNativeViewMounted:viewTag];
if (lastSnapshot != nil) {
NSMutableDictionary *lastProps = lastSnapshot.props;
for (NSString *key in props) {
[lastProps setValue:props[key] forKey:key];
}
}
// If the component isn't mounted, we will bail early with a scheduled update
if (!isNativeViewMounted) {
if (lastSnapshot == nil) {
ComponentUpdate *propsSnapshot = [ComponentUpdate new];
propsSnapshot.props = [props mutableCopy];
propsSnapshot.viewTag = viewTag;
propsSnapshot.viewName = viewName;
_componentUpdateBuffer[viewTag] = propsSnapshot;
atomic_store(&_shouldFlushUpdateBuffer, true);
}
return;
}
// The component may have been mounted with a pending snapshot (due to a race condition),
// so we should attempt run the update. Otherwise, the next call to -maybeFlushUpdateBuffer
// will only arrive when a new component is mounted (which might be never!)
//
// If there are 0 remaining items in the buffer, we can skip the run in -maybeFlushUpdateBuffer.
if (lastSnapshot != nil && isNativeViewMounted) {
props = lastSnapshot.props;
viewTag = lastSnapshot.viewTag;
viewName = lastSnapshot.viewName;
[_componentUpdateBuffer removeObjectForKey:viewTag];
if (_componentUpdateBuffer.count == 0) {
atomic_store(&_shouldFlushUpdateBuffer, false);
}
}
// TODO: refactor PropsNode to also use this function
NSMutableDictionary *uiProps = [NSMutableDictionary new];
NSMutableDictionary *nativeProps = [NSMutableDictionary new];
NSMutableDictionary *jsProps = [NSMutableDictionary new];
void (^addBlock)(NSString *key, id obj, BOOL *stop) = ^(NSString *key, id obj, BOOL *stop) {
if ([self.uiProps containsObject:key]) {
uiProps[key] = obj;
} else if ([self.nativeProps containsObject:key]) {
nativeProps[key] = obj;
} else {
jsProps[key] = obj;
}
};
[props enumerateKeysAndObjectsUsingBlock:addBlock];
if (uiProps.count > 0) {
[self.uiManager synchronouslyUpdateViewOnUIThread:viewTag viewName:viewName props:uiProps];
}
if (nativeProps.count > 0) {
[self enqueueUpdateViewOnNativeThread:viewTag viewName:viewName nativeProps:nativeProps trySynchronously:YES];
}
if (jsProps.count > 0) {
[self.reanimatedModule sendEventWithName:@"onReanimatedPropsChange"
body:@{@"viewTag" : viewTag, @"props" : jsProps}];
}
}
- (NSString *)obtainProp:(nonnull NSNumber *)viewTag propName:(nonnull NSString *)propName
{
REAUIView *view = [self.uiManager viewForReactTag:viewTag];
if ([propName isEqualToString:@"opacity"]) {
#if !TARGET_OS_OSX
CGFloat alpha = view.alpha;
#else
CGFloat alpha = view.alphaValue;
#endif // TARGET_OS_OSX
return [@(alpha) stringValue];
} else if ([propName isEqualToString:@"zIndex"]) {
NSInteger zIndex = view.reactZIndex;
return [@(zIndex) stringValue];
} else if ([propName isEqualToString:@"width"]) {
return [@(view.frame.size.width) stringValue];
} else if ([propName isEqualToString:@"height"]) {
return [@(view.frame.size.height) stringValue];
} else if ([propName isEqualToString:@"top"]) {
return [@(view.frame.origin.y) stringValue];
} else if ([propName isEqualToString:@"left"]) {
return [@(view.frame.origin.x) stringValue];
} else if ([propName isEqualToString:@"backgroundColor"]) {
#if !TARGET_OS_OSX
UIColor *color = view.backgroundColor;
#else
NSColor *color = view.backgroundColor;
#endif
if (color == nil) {
return @"nil";
}
const size_t totalComponents = CGColorGetNumberOfComponents(color.CGColor);
const CGFloat *components = CGColorGetComponents(color.CGColor);
int r = 255 * components[MIN(0, totalComponents - 1)];
int g = 255 * components[MIN(1, totalComponents - 1)];
int b = 255 * components[MIN(2, totalComponents - 1)];
int alpha = 255 * components[MIN(3, totalComponents - 1)];
return [NSString stringWithFormat:@"#%02x%02x%02x%02x", r, g, b, alpha];
}
return [NSString
stringWithFormat:
@"error: unknown propName %@, currently supported: opacity, zIndex, width, height, top, left, backgroundColor",
propName];
}
- (void)maybeFlushUpdateBuffer
{
RCTAssertUIManagerQueue();
bool shouldFlushUpdateBuffer = atomic_load(&_shouldFlushUpdateBuffer);
if (!shouldFlushUpdateBuffer) {
return;
}
__weak __typeof__(self) weakSelf = self;
[_uiManager
addUIBlock:^(__unused RCTUIManager *manager, __unused NSDictionary<NSNumber *, REAUIView *> *viewRegistry) {
__typeof__(self) strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
atomic_store(&strongSelf->_shouldFlushUpdateBuffer, false);
NSMutableDictionary *componentUpdateBuffer = [strongSelf->_componentUpdateBuffer copy];
strongSelf->_componentUpdateBuffer = [NSMutableDictionary new];
for (NSNumber *tag in componentUpdateBuffer) {
ComponentUpdate *componentUpdate = componentUpdateBuffer[tag];
if (componentUpdate == Nil) {
continue;
}
[strongSelf updateProps:componentUpdate.props
ofViewWithTag:componentUpdate.viewTag
withName:componentUpdate.viewName];
}
[strongSelf performOperations];
}];
}
#endif // RCT_NEW_ARCH_ENABLED
- (void)maybeFlushUIUpdatesQueue
{
if ([[self getDisplayLink] isPaused]) {
[self performOperations];
}
}
@end

View File

@@ -0,0 +1,6 @@
namespace reanimated {
CGFloat getUIAnimationDragCoefficient(void);
CFTimeInterval calculateTimestampWithSlowAnimations(CFTimeInterval currentTimestamp);
} // namespace reanimated

View File

@@ -0,0 +1,44 @@
#import <QuartzCore/QuartzCore.h>
#import <RNReanimated/REASlowAnimations.h>
#if TARGET_IPHONE_SIMULATOR
#import <dlfcn.h>
#endif
namespace reanimated {
CGFloat getUIAnimationDragCoefficient(void)
{
static float (*UIAnimationDragCoefficient)(void) = NULL;
#if TARGET_IPHONE_SIMULATOR
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
UIAnimationDragCoefficient = (float (*)(void))dlsym(RTLD_DEFAULT, "UIAnimationDragCoefficient");
});
#endif
return UIAnimationDragCoefficient ? UIAnimationDragCoefficient() : 1.f;
}
CFTimeInterval calculateTimestampWithSlowAnimations(CFTimeInterval currentTimestamp)
{
#if TARGET_IPHONE_SIMULATOR
static CFTimeInterval dragCoefChangedTimestamp = CACurrentMediaTime();
static CGFloat previousDragCoef = getUIAnimationDragCoefficient();
const CGFloat dragCoef = getUIAnimationDragCoefficient();
if (previousDragCoef != dragCoef) {
previousDragCoef = dragCoef;
dragCoefChangedTimestamp = CACurrentMediaTime();
}
const bool areSlowAnimationsEnabled = dragCoef != 1.f;
if (areSlowAnimationsEnabled) {
return (dragCoefChangedTimestamp + (currentTimestamp - dragCoefChangedTimestamp) / dragCoef);
} else {
return currentTimestamp;
}
#else
return currentTimestamp;
#endif
}
} // namespace reanimated

View File

@@ -0,0 +1,13 @@
#if !TARGET_OS_OSX
#import <UIKit/UIKit.h>
typedef UIView REAUIView;
#else // TARGET_OS_OSX [
#import <RNReanimated/RCTUIView+Reanimated.h>
typedef RCTUIView REAUIView;
#endif // ] TARGET_OS_OSX

View File

@@ -0,0 +1,8 @@
#import <objc/runtime.h>
@interface REAUtils : NSObject
+ (void)swizzleMethod:(SEL)originalSelector
forClass:(Class)originalClass
with:(SEL)newSelector
fromClass:(Class)newClass;
@end

View File

@@ -0,0 +1,18 @@
#import <RNReanimated/REAUtils.h>
@implementation REAUtils
+ (void)swizzleMethod:(SEL)originalSelector
forClass:(Class)originalClass
with:(SEL)newSelector
fromClass:(Class)newClass
{
Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
Method newMethod = class_getInstanceMethod(newClass, newSelector);
IMP originalImplementation = method_getImplementation(originalMethod);
IMP newImplementation = method_getImplementation(newMethod);
class_replaceMethod(originalClass, newSelector, originalImplementation, method_getTypeEncoding(originalMethod));
class_replaceMethod(originalClass, originalSelector, newImplementation, method_getTypeEncoding(newMethod));
}
@end

View File

@@ -0,0 +1,5 @@
@protocol RNGestureHandlerStateManager
- (void)setGestureState:(int)state forHandler:(int)handlerTag;
@end

View File

@@ -0,0 +1,611 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
440FEC2C209062F100EEC73A /* REAPropsNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC0D209062F100EEC73A /* REAPropsNode.m */; };
440FEC2D209062F100EEC73A /* REAStyleNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC13209062F100EEC73A /* REAStyleNode.m */; };
440FEC2E209062F100EEC73A /* READebugNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC14209062F100EEC73A /* READebugNode.m */; };
440FEC2F209062F100EEC73A /* REAClockNodes.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC18209062F100EEC73A /* REAClockNodes.m */; };
440FEC30209062F100EEC73A /* REAValueNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC19209062F100EEC73A /* REAValueNode.m */; };
440FEC31209062F100EEC73A /* REACondNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC1A209062F100EEC73A /* REACondNode.m */; };
440FEC32209062F100EEC73A /* REAJSCallNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC1B209062F100EEC73A /* REAJSCallNode.m */; };
440FEC33209062F100EEC73A /* REABezierNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC1C209062F100EEC73A /* REABezierNode.m */; };
440FEC34209062F100EEC73A /* REANode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC1F209062F100EEC73A /* REANode.m */; };
440FEC35209062F100EEC73A /* REATransformNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC20209062F100EEC73A /* REATransformNode.m */; };
440FEC36209062F100EEC73A /* REAEventNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC21209062F100EEC73A /* REAEventNode.m */; };
440FEC37209062F100EEC73A /* REASetNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC23209062F100EEC73A /* REASetNode.m */; };
440FEC38209062F100EEC73A /* REAOperatorNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC24209062F100EEC73A /* REAOperatorNode.m */; };
440FEC39209062F100EEC73A /* REABlockNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC25209062F100EEC73A /* REABlockNode.m */; };
440FEC3A209062F100EEC73A /* REAModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC2A209062F100EEC73A /* REAModule.m */; };
440FEC3B209062F100EEC73A /* REANodesManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC2B209062F100EEC73A /* REANodesManager.m */; };
44125DC022538E6D003C1762 /* REATransitionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 44125DBF22538E6D003C1762 /* REATransitionManager.m */; };
44125DC322538F68003C1762 /* REATransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 44125DC222538F68003C1762 /* REATransition.m */; };
44125DC82253906B003C1762 /* REAAllTransitions.m in Sources */ = {isa = PBXBuildFile; fileRef = 44125DC72253906A003C1762 /* REAAllTransitions.m */; };
44125DCB22539177003C1762 /* REATransitionAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 44125DCA22539177003C1762 /* REATransitionAnimation.m */; };
44125DCE2253A038003C1762 /* REATransitionValues.m in Sources */ = {isa = PBXBuildFile; fileRef = 44125DCD2253A038003C1762 /* REATransitionValues.m */; };
44125DD12253A3C0003C1762 /* RCTConvert+REATransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 44125DD02253A3C0003C1762 /* RCTConvert+REATransition.m */; };
660A44292119B821006BFD5E /* REAConcatNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 660A44282119B820006BFD5E /* REAConcatNode.m */; };
66240C6920C68DEA00648F55 /* REAAlwaysNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 66240C6820C68DEA00648F55 /* REAAlwaysNode.m */; };
A12DA6B722EC228D00E8271A /* REAParamNode.m in Sources */ = {isa = PBXBuildFile; fileRef = A12DA6B622EC228D00E8271A /* REAParamNode.m */; };
A12DA6B822EC228D00E8271A /* REAParamNode.m in Sources */ = {isa = PBXBuildFile; fileRef = A12DA6B622EC228D00E8271A /* REAParamNode.m */; };
A12DA6BE22EC22E400E8271A /* REAFunctionNode.m in Sources */ = {isa = PBXBuildFile; fileRef = A12DA6BA22EC22E300E8271A /* REAFunctionNode.m */; };
A12DA6BF22EC22E400E8271A /* REAFunctionNode.m in Sources */ = {isa = PBXBuildFile; fileRef = A12DA6BA22EC22E300E8271A /* REAFunctionNode.m */; };
A12DA6C022EC22E400E8271A /* REACallFuncNode.m in Sources */ = {isa = PBXBuildFile; fileRef = A12DA6BC22EC22E300E8271A /* REACallFuncNode.m */; };
A12DA6C122EC22E400E8271A /* REACallFuncNode.m in Sources */ = {isa = PBXBuildFile; fileRef = A12DA6BC22EC22E300E8271A /* REACallFuncNode.m */; };
FDBB1777229BF0DE00D1E455 /* REAModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC2A209062F100EEC73A /* REAModule.m */; };
FDBB1778229BF0DE00D1E455 /* REANodesManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC2B209062F100EEC73A /* REANodesManager.m */; };
FDBB1779229BF0E700D1E455 /* REATransitionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 44125DBF22538E6D003C1762 /* REATransitionManager.m */; };
FDBB177A229BF0E700D1E455 /* REATransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 44125DC222538F68003C1762 /* REATransition.m */; };
FDBB177B229BF0E700D1E455 /* REAAllTransitions.m in Sources */ = {isa = PBXBuildFile; fileRef = 44125DC72253906A003C1762 /* REAAllTransitions.m */; };
FDBB177C229BF0E700D1E455 /* REATransitionAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = 44125DCA22539177003C1762 /* REATransitionAnimation.m */; };
FDBB177D229BF0E700D1E455 /* REATransitionValues.m in Sources */ = {isa = PBXBuildFile; fileRef = 44125DCD2253A038003C1762 /* REATransitionValues.m */; };
FDBB177E229BF0E700D1E455 /* RCTConvert+REATransition.m in Sources */ = {isa = PBXBuildFile; fileRef = 44125DD02253A3C0003C1762 /* RCTConvert+REATransition.m */; };
FDE6D933229BF70F007F6716 /* REAConcatNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 660A44282119B820006BFD5E /* REAConcatNode.m */; };
FDE6D934229BF70F007F6716 /* REAAlwaysNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 66240C6820C68DEA00648F55 /* REAAlwaysNode.m */; };
FDE6D935229BF70F007F6716 /* REANode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC1F209062F100EEC73A /* REANode.m */; };
FDE6D936229BF70F007F6716 /* REAValueNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC19209062F100EEC73A /* REAValueNode.m */; };
FDE6D937229BF70F007F6716 /* REAStyleNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC13209062F100EEC73A /* REAStyleNode.m */; };
FDE6D938229BF70F007F6716 /* REATransformNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC20209062F100EEC73A /* REATransformNode.m */; };
FDE6D939229BF70F007F6716 /* REAPropsNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC0D209062F100EEC73A /* REAPropsNode.m */; };
FDE6D93A229BF70F007F6716 /* REABlockNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC25209062F100EEC73A /* REABlockNode.m */; };
FDE6D93B229BF70F007F6716 /* REACondNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC1A209062F100EEC73A /* REACondNode.m */; };
FDE6D93C229BF70F007F6716 /* REAOperatorNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC24209062F100EEC73A /* REAOperatorNode.m */; };
FDE6D93D229BF70F007F6716 /* REASetNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC23209062F100EEC73A /* REASetNode.m */; };
FDE6D93E229BF70F007F6716 /* READebugNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC14209062F100EEC73A /* READebugNode.m */; };
FDE6D93F229BF70F007F6716 /* REAClockNodes.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC18209062F100EEC73A /* REAClockNodes.m */; };
FDE6D940229BF70F007F6716 /* REAJSCallNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC1B209062F100EEC73A /* REAJSCallNode.m */; };
FDE6D941229BF70F007F6716 /* REABezierNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC1C209062F100EEC73A /* REABezierNode.m */; };
FDE6D942229BF70F007F6716 /* REAEventNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 440FEC21209062F100EEC73A /* REAEventNode.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
58B511D91A9E6C8500147676 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
FDBB176C229BF04900D1E455 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
134814201AA4EA6300B7C361 /* libRNReanimated.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNReanimated.a; sourceTree = BUILT_PRODUCTS_DIR; };
440FEC01209062F100EEC73A /* REANodesManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REANodesManager.h; sourceTree = "<group>"; };
440FEC0A209062F100EEC73A /* REAModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REAModule.h; sourceTree = "<group>"; };
440FEC0D209062F100EEC73A /* REAPropsNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REAPropsNode.m; sourceTree = "<group>"; };
440FEC0E209062F100EEC73A /* REABezierNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REABezierNode.h; sourceTree = "<group>"; };
440FEC0F209062F100EEC73A /* REAJSCallNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REAJSCallNode.h; sourceTree = "<group>"; };
440FEC10209062F100EEC73A /* REAEventNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REAEventNode.h; sourceTree = "<group>"; };
440FEC11209062F100EEC73A /* REANode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REANode.h; sourceTree = "<group>"; };
440FEC12209062F100EEC73A /* REATransformNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REATransformNode.h; sourceTree = "<group>"; };
440FEC13209062F100EEC73A /* REAStyleNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REAStyleNode.m; sourceTree = "<group>"; };
440FEC14209062F100EEC73A /* READebugNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = READebugNode.m; sourceTree = "<group>"; };
440FEC15209062F100EEC73A /* REABlockNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REABlockNode.h; sourceTree = "<group>"; };
440FEC16209062F100EEC73A /* REAOperatorNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REAOperatorNode.h; sourceTree = "<group>"; };
440FEC17209062F100EEC73A /* REASetNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REASetNode.h; sourceTree = "<group>"; };
440FEC18209062F100EEC73A /* REAClockNodes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REAClockNodes.m; sourceTree = "<group>"; };
440FEC19209062F100EEC73A /* REAValueNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REAValueNode.m; sourceTree = "<group>"; };
440FEC1A209062F100EEC73A /* REACondNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REACondNode.m; sourceTree = "<group>"; };
440FEC1B209062F100EEC73A /* REAJSCallNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REAJSCallNode.m; sourceTree = "<group>"; };
440FEC1C209062F100EEC73A /* REABezierNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REABezierNode.m; sourceTree = "<group>"; };
440FEC1D209062F100EEC73A /* REAPropsNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REAPropsNode.h; sourceTree = "<group>"; };
440FEC1E209062F100EEC73A /* REAStyleNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REAStyleNode.h; sourceTree = "<group>"; };
440FEC1F209062F100EEC73A /* REANode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REANode.m; sourceTree = "<group>"; };
440FEC20209062F100EEC73A /* REATransformNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REATransformNode.m; sourceTree = "<group>"; };
440FEC21209062F100EEC73A /* REAEventNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REAEventNode.m; sourceTree = "<group>"; };
440FEC22209062F100EEC73A /* REAClockNodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REAClockNodes.h; sourceTree = "<group>"; };
440FEC23209062F100EEC73A /* REASetNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REASetNode.m; sourceTree = "<group>"; };
440FEC24209062F100EEC73A /* REAOperatorNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REAOperatorNode.m; sourceTree = "<group>"; };
440FEC25209062F100EEC73A /* REABlockNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REABlockNode.m; sourceTree = "<group>"; };
440FEC26209062F100EEC73A /* READebugNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = READebugNode.h; sourceTree = "<group>"; };
440FEC27209062F100EEC73A /* REACondNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REACondNode.h; sourceTree = "<group>"; };
440FEC28209062F100EEC73A /* REAValueNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REAValueNode.h; sourceTree = "<group>"; };
440FEC2A209062F100EEC73A /* REAModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REAModule.m; sourceTree = "<group>"; };
440FEC2B209062F100EEC73A /* REANodesManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REANodesManager.m; sourceTree = "<group>"; };
44125DBE22538E6D003C1762 /* REATransitionManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = REATransitionManager.h; sourceTree = "<group>"; };
44125DBF22538E6D003C1762 /* REATransitionManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = REATransitionManager.m; sourceTree = "<group>"; };
44125DC122538F68003C1762 /* REATransition.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = REATransition.h; sourceTree = "<group>"; };
44125DC222538F68003C1762 /* REATransition.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = REATransition.m; sourceTree = "<group>"; };
44125DC62253906A003C1762 /* REAAllTransitions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = REAAllTransitions.h; sourceTree = "<group>"; };
44125DC72253906A003C1762 /* REAAllTransitions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = REAAllTransitions.m; sourceTree = "<group>"; };
44125DC922539177003C1762 /* REATransitionAnimation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = REATransitionAnimation.h; sourceTree = "<group>"; };
44125DCA22539177003C1762 /* REATransitionAnimation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = REATransitionAnimation.m; sourceTree = "<group>"; };
44125DCC2253A038003C1762 /* REATransitionValues.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = REATransitionValues.h; sourceTree = "<group>"; };
44125DCD2253A038003C1762 /* REATransitionValues.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = REATransitionValues.m; sourceTree = "<group>"; };
44125DCF2253A3C0003C1762 /* RCTConvert+REATransition.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+REATransition.h"; sourceTree = "<group>"; };
44125DD02253A3C0003C1762 /* RCTConvert+REATransition.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+REATransition.m"; sourceTree = "<group>"; };
4DA383A8226FA6A400582919 /* REAUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = REAUtils.h; sourceTree = "<group>"; };
660A44282119B820006BFD5E /* REAConcatNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REAConcatNode.m; sourceTree = "<group>"; };
660A442A2119B83E006BFD5E /* REAConcatNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REAConcatNode.h; sourceTree = "<group>"; };
66240C6720C68DEA00648F55 /* REAAlwaysNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REAAlwaysNode.h; sourceTree = "<group>"; };
66240C6820C68DEA00648F55 /* REAAlwaysNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REAAlwaysNode.m; sourceTree = "<group>"; };
A12DA6B622EC228D00E8271A /* REAParamNode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = REAParamNode.m; sourceTree = "<group>"; };
A12DA6B922EC22A900E8271A /* REAParamNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = REAParamNode.h; sourceTree = "<group>"; };
A12DA6BA22EC22E300E8271A /* REAFunctionNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REAFunctionNode.m; sourceTree = "<group>"; };
A12DA6BB22EC22E300E8271A /* REACallFuncNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REACallFuncNode.h; sourceTree = "<group>"; };
A12DA6BC22EC22E300E8271A /* REACallFuncNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = REACallFuncNode.m; sourceTree = "<group>"; };
A12DA6BD22EC22E300E8271A /* REAFunctionNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = REAFunctionNode.h; sourceTree = "<group>"; };
FDBB176E229BF04900D1E455 /* libRNReanimated-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRNReanimated-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
58B511D81A9E6C8500147676 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
FDBB176B229BF04900D1E455 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
134814211AA4EA7D00B7C361 /* Products */ = {
isa = PBXGroup;
children = (
134814201AA4EA6300B7C361 /* libRNReanimated.a */,
);
name = Products;
sourceTree = "<group>";
};
440FEC0C209062F100EEC73A /* Nodes */ = {
isa = PBXGroup;
children = (
A12DA6B922EC22A900E8271A /* REAParamNode.h */,
A12DA6B622EC228D00E8271A /* REAParamNode.m */,
A12DA6BB22EC22E300E8271A /* REACallFuncNode.h */,
A12DA6BC22EC22E300E8271A /* REACallFuncNode.m */,
A12DA6BD22EC22E300E8271A /* REAFunctionNode.h */,
A12DA6BA22EC22E300E8271A /* REAFunctionNode.m */,
660A442A2119B83E006BFD5E /* REAConcatNode.h */,
660A44282119B820006BFD5E /* REAConcatNode.m */,
66240C6720C68DEA00648F55 /* REAAlwaysNode.h */,
66240C6820C68DEA00648F55 /* REAAlwaysNode.m */,
440FEC11209062F100EEC73A /* REANode.h */,
440FEC1F209062F100EEC73A /* REANode.m */,
440FEC28209062F100EEC73A /* REAValueNode.h */,
440FEC19209062F100EEC73A /* REAValueNode.m */,
440FEC1E209062F100EEC73A /* REAStyleNode.h */,
440FEC13209062F100EEC73A /* REAStyleNode.m */,
440FEC12209062F100EEC73A /* REATransformNode.h */,
440FEC20209062F100EEC73A /* REATransformNode.m */,
440FEC1D209062F100EEC73A /* REAPropsNode.h */,
440FEC0D209062F100EEC73A /* REAPropsNode.m */,
440FEC15209062F100EEC73A /* REABlockNode.h */,
440FEC25209062F100EEC73A /* REABlockNode.m */,
440FEC27209062F100EEC73A /* REACondNode.h */,
440FEC1A209062F100EEC73A /* REACondNode.m */,
440FEC16209062F100EEC73A /* REAOperatorNode.h */,
440FEC24209062F100EEC73A /* REAOperatorNode.m */,
440FEC17209062F100EEC73A /* REASetNode.h */,
440FEC23209062F100EEC73A /* REASetNode.m */,
440FEC26209062F100EEC73A /* READebugNode.h */,
440FEC14209062F100EEC73A /* READebugNode.m */,
440FEC22209062F100EEC73A /* REAClockNodes.h */,
440FEC18209062F100EEC73A /* REAClockNodes.m */,
440FEC0F209062F100EEC73A /* REAJSCallNode.h */,
440FEC1B209062F100EEC73A /* REAJSCallNode.m */,
440FEC0E209062F100EEC73A /* REABezierNode.h */,
440FEC1C209062F100EEC73A /* REABezierNode.m */,
440FEC10209062F100EEC73A /* REAEventNode.h */,
440FEC21209062F100EEC73A /* REAEventNode.m */,
);
path = Nodes;
sourceTree = "<group>";
};
44125C76224FBE0B003C1762 /* Transitioning */ = {
isa = PBXGroup;
children = (
44125DBE22538E6D003C1762 /* REATransitionManager.h */,
44125DBF22538E6D003C1762 /* REATransitionManager.m */,
44125DC122538F68003C1762 /* REATransition.h */,
44125DC222538F68003C1762 /* REATransition.m */,
44125DC62253906A003C1762 /* REAAllTransitions.h */,
44125DC72253906A003C1762 /* REAAllTransitions.m */,
44125DC922539177003C1762 /* REATransitionAnimation.h */,
44125DCA22539177003C1762 /* REATransitionAnimation.m */,
44125DCC2253A038003C1762 /* REATransitionValues.h */,
44125DCD2253A038003C1762 /* REATransitionValues.m */,
44125DCF2253A3C0003C1762 /* RCTConvert+REATransition.h */,
44125DD02253A3C0003C1762 /* RCTConvert+REATransition.m */,
);
path = Transitioning;
sourceTree = "<group>";
};
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
4DA383A8226FA6A400582919 /* REAUtils.h */,
44125C76224FBE0B003C1762 /* Transitioning */,
440FEC0C209062F100EEC73A /* Nodes */,
440FEC0A209062F100EEC73A /* REAModule.h */,
440FEC2A209062F100EEC73A /* REAModule.m */,
440FEC01209062F100EEC73A /* REANodesManager.h */,
440FEC2B209062F100EEC73A /* REANodesManager.m */,
134814211AA4EA7D00B7C361 /* Products */,
FDBB176E229BF04900D1E455 /* libRNReanimated-tvOS.a */,
);
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
58B511DA1A9E6C8500147676 /* RNReanimated */ = {
isa = PBXNativeTarget;
buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNReanimated" */;
buildPhases = (
58B511D71A9E6C8500147676 /* Sources */,
58B511D81A9E6C8500147676 /* Frameworks */,
58B511D91A9E6C8500147676 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = RNReanimated;
productName = RCTDataManager;
productReference = 134814201AA4EA6300B7C361 /* libRNReanimated.a */;
productType = "com.apple.product-type.library.static";
};
FDBB176D229BF04900D1E455 /* RNReanimated-tvOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = FDBB1774229BF04900D1E455 /* Build configuration list for PBXNativeTarget "RNReanimated-tvOS" */;
buildPhases = (
FDBB176A229BF04900D1E455 /* Sources */,
FDBB176B229BF04900D1E455 /* Frameworks */,
FDBB176C229BF04900D1E455 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = "RNReanimated-tvOS";
productName = "RNReanimated-tvOS";
productReference = FDBB176E229BF04900D1E455 /* libRNReanimated-tvOS.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
58B511D31A9E6C8500147676 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0920;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
58B511DA1A9E6C8500147676 = {
CreatedOnToolsVersion = 6.1.1;
};
FDBB176D229BF04900D1E455 = {
CreatedOnToolsVersion = 10.2.1;
DevelopmentTeam = NZDV3AFAEG;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNReanimated" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = 58B511D21A9E6C8500147676;
productRefGroup = 58B511D21A9E6C8500147676;
projectDirPath = "";
projectRoot = "";
targets = (
58B511DA1A9E6C8500147676 /* RNReanimated */,
FDBB176D229BF04900D1E455 /* RNReanimated-tvOS */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
58B511D71A9E6C8500147676 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
440FEC2E209062F100EEC73A /* READebugNode.m in Sources */,
440FEC37209062F100EEC73A /* REASetNode.m in Sources */,
440FEC3A209062F100EEC73A /* REAModule.m in Sources */,
44125DCE2253A038003C1762 /* REATransitionValues.m in Sources */,
44125DC82253906B003C1762 /* REAAllTransitions.m in Sources */,
A12DA6B722EC228D00E8271A /* REAParamNode.m in Sources */,
44125DC022538E6D003C1762 /* REATransitionManager.m in Sources */,
440FEC39209062F100EEC73A /* REABlockNode.m in Sources */,
A12DA6C022EC22E400E8271A /* REACallFuncNode.m in Sources */,
440FEC33209062F100EEC73A /* REABezierNode.m in Sources */,
44125DC322538F68003C1762 /* REATransition.m in Sources */,
440FEC2D209062F100EEC73A /* REAStyleNode.m in Sources */,
440FEC31209062F100EEC73A /* REACondNode.m in Sources */,
44125DD12253A3C0003C1762 /* RCTConvert+REATransition.m in Sources */,
440FEC2C209062F100EEC73A /* REAPropsNode.m in Sources */,
A12DA6BE22EC22E400E8271A /* REAFunctionNode.m in Sources */,
440FEC3B209062F100EEC73A /* REANodesManager.m in Sources */,
440FEC36209062F100EEC73A /* REAEventNode.m in Sources */,
44125DCB22539177003C1762 /* REATransitionAnimation.m in Sources */,
66240C6920C68DEA00648F55 /* REAAlwaysNode.m in Sources */,
440FEC2F209062F100EEC73A /* REAClockNodes.m in Sources */,
660A44292119B821006BFD5E /* REAConcatNode.m in Sources */,
440FEC38209062F100EEC73A /* REAOperatorNode.m in Sources */,
440FEC34209062F100EEC73A /* REANode.m in Sources */,
440FEC30209062F100EEC73A /* REAValueNode.m in Sources */,
440FEC32209062F100EEC73A /* REAJSCallNode.m in Sources */,
440FEC35209062F100EEC73A /* REATransformNode.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
FDBB176A229BF04900D1E455 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
FDE6D93C229BF70F007F6716 /* REAOperatorNode.m in Sources */,
FDE6D93F229BF70F007F6716 /* REAClockNodes.m in Sources */,
FDE6D936229BF70F007F6716 /* REAValueNode.m in Sources */,
FDE6D941229BF70F007F6716 /* REABezierNode.m in Sources */,
FDBB177B229BF0E700D1E455 /* REAAllTransitions.m in Sources */,
A12DA6B822EC228D00E8271A /* REAParamNode.m in Sources */,
FDE6D93D229BF70F007F6716 /* REASetNode.m in Sources */,
FDE6D93A229BF70F007F6716 /* REABlockNode.m in Sources */,
A12DA6C122EC22E400E8271A /* REACallFuncNode.m in Sources */,
FDE6D939229BF70F007F6716 /* REAPropsNode.m in Sources */,
FDBB1778229BF0DE00D1E455 /* REANodesManager.m in Sources */,
FDE6D937229BF70F007F6716 /* REAStyleNode.m in Sources */,
FDE6D940229BF70F007F6716 /* REAJSCallNode.m in Sources */,
FDE6D935229BF70F007F6716 /* REANode.m in Sources */,
FDE6D93B229BF70F007F6716 /* REACondNode.m in Sources */,
A12DA6BF22EC22E400E8271A /* REAFunctionNode.m in Sources */,
FDBB177C229BF0E700D1E455 /* REATransitionAnimation.m in Sources */,
FDE6D934229BF70F007F6716 /* REAAlwaysNode.m in Sources */,
FDE6D933229BF70F007F6716 /* REAConcatNode.m in Sources */,
FDBB177D229BF0E700D1E455 /* REATransitionValues.m in Sources */,
FDE6D942229BF70F007F6716 /* REAEventNode.m in Sources */,
FDE6D93E229BF70F007F6716 /* READebugNode.m in Sources */,
FDE6D938229BF70F007F6716 /* REATransformNode.m in Sources */,
FDBB177A229BF0E700D1E455 /* REATransition.m in Sources */,
FDBB177E229BF0E700D1E455 /* RCTConvert+REATransition.m in Sources */,
FDBB1777229BF0DE00D1E455 /* REAModule.m in Sources */,
FDBB1779229BF0E700D1E455 /* REATransitionManager.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
58B511ED1A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
58B511EE1A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
GCC_PREPROCESSOR_DEFINITIONS = (
"NDEBUG=1",
);
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
58B511F01A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNReanimated;
SKIP_INSTALL = YES;
};
name = Debug;
};
58B511F11A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNReanimated;
SKIP_INSTALL = YES;
};
name = Release;
};
FDBB1775229BF04900D1E455 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = NZDV3AFAEG;
GCC_C_LANGUAGE_STANDARD = gnu11;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 12.2;
};
name = Debug;
};
FDBB1776229BF04900D1E455 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = NZDV3AFAEG;
GCC_C_LANGUAGE_STANDARD = gnu11;
MTL_FAST_MATH = YES;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = appletvos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 12.2;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNReanimated" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511ED1A9E6C8500147676 /* Debug */,
58B511EE1A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNReanimated" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511F01A9E6C8500147676 /* Debug */,
58B511F11A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
FDBB1774229BF04900D1E455 /* Build configuration list for PBXNativeTarget "RNReanimated-tvOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
FDBB1775229BF04900D1E455 /* Debug */,
FDBB1776229BF04900D1E455 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 58B511D31A9E6C8500147676 /* Project object */;
}

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FDBB176D229BF04900D1E455"
BuildableName = "libRNReanimated-tvOS.a"
BlueprintName = "RNReanimated-tvOS"
ReferencedContainer = "container:RNReanimated.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FDBB176D229BF04900D1E455"
BuildableName = "libRNReanimated-tvOS.a"
BlueprintName = "RNReanimated-tvOS"
ReferencedContainer = "container:RNReanimated.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FDBB176D229BF04900D1E455"
BuildableName = "libRNReanimated-tvOS.a"
BlueprintName = "RNReanimated-tvOS"
ReferencedContainer = "container:RNReanimated.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,11 @@
#import <React/RCTEventDispatcher.h>
typedef void (^KeyboardEventListenerBlock)(int keyboardState, int height);
@interface REAKeyboardEventObserver : NSObject
- (instancetype)init;
- (int)subscribeForKeyboardEvents:(KeyboardEventListenerBlock)listener;
- (void)unsubscribeFromKeyboardEvents:(int)listenerId;
@end

View File

@@ -0,0 +1,378 @@
#import <Foundation/Foundation.h>
#import <RNReanimated/READisplayLink.h>
#import <RNReanimated/REAKeyboardEventObserver.h>
#import <RNReanimated/REASlowAnimations.h>
#import <RNReanimated/REAUIKit.h>
#import <React/RCTDefines.h>
#import <React/RCTUIManager.h>
typedef NS_ENUM(NSUInteger, KeyboardState) {
UNKNOWN = 0,
OPENING = 1,
OPEN = 2,
CLOSING = 3,
CLOSED = 4,
};
@implementation REAKeyboardEventObserver {
REAUIView *_measuringView;
NSNumber *_nextListenerId;
NSMutableDictionary *_listeners;
READisplayLink *_displayLink;
KeyboardState _state;
CFTimeInterval _animationStartTimestamp;
float _initialKeyboardHeight;
float _targetKeyboardHeight;
REAUIView *_keyboardView;
bool _isKeyboardObserverAttached;
}
- (instancetype)init
{
self = [super init];
_listeners = [[NSMutableDictionary alloc] init];
_nextListenerId = @0;
_state = UNKNOWN;
_animationStartTimestamp = 0;
_isKeyboardObserverAttached = false;
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:@selector(cleanupListeners)
name:RCTBridgeDidInvalidateModulesNotification
object:nil];
return self;
}
- (READisplayLink *)getDisplayLink
{
RCTAssertMainQueue();
if (!_displayLink) {
_displayLink = [READisplayLink displayLinkWithTarget:self selector:@selector(updateKeyboardFrame)];
#if !TARGET_OS_OSX
_displayLink.preferredFramesPerSecond = 120; // will fallback to 60 fps for devices without Pro Motion display
#endif
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
return _displayLink;
}
#if TARGET_OS_TV
- (int)subscribeForKeyboardEvents:(KeyboardEventListenerBlock)listener
{
NSLog(@"Keyboard handling is not supported on tvOS");
return 0;
}
- (void)unsubscribeFromKeyboardEvents:(int)listenerId
{
NSLog(@"Keyboard handling is not supported on tvOS");
}
#elif TARGET_OS_OSX
- (int)subscribeForKeyboardEvents:(KeyboardEventListenerBlock)listener
{
NSLog(@"Keyboard handling is not supported on macOS");
return 0;
}
- (void)unsubscribeFromKeyboardEvents:(int)listenerId
{
NSLog(@"Keyboard handling is not supported on macOS");
}
#else
- (void)runListeners:(float)keyboardHeight
{
for (NSString *key in _listeners.allKeys) {
((KeyboardEventListenerBlock)_listeners[key])(_state, keyboardHeight);
}
}
- (void)runUpdater
{
[[self getDisplayLink] setPaused:NO];
_animationStartTimestamp = 0;
}
- (float)getTargetTimestamp
{
float targetTimestamp = _displayLink.targetTimestamp;
return reanimated::calculateTimestampWithSlowAnimations(targetTimestamp) * 1000;
}
- (float)estimateProgressForDuration:(float)keyboardAnimationDuration
a1:(float)a1
a2:(float)a2
b1:(float)b1
b2:(float)b2
c1:(float)c1
c2:(float)c2
{
CFTimeInterval elapsedTime = _displayLink.targetTimestamp - _animationStartTimestamp;
float timeProgress = elapsedTime / keyboardAnimationDuration;
timeProgress = fmax(fmin(timeProgress, 1), 0);
float x = timeProgress;
float progress = 1 - a1 * pow(1 - x, a2) - b1 * x * pow(1 - x, b2) - c1 * pow(x, 2) * pow(1 - x, c2);
return progress;
}
- (CGFloat)estimateOpeningKeyboardHeight
{
/*
Curve parameters come from estimation: https://www.desmos.com/calculator/ufy5rbucpd
Animation takes 30 frames, which is 0.48 seconds at 60 fps.
*/
float progress = [self estimateProgressForDuration:0.48 a1:1 a2:4.62 b1:2.44 b2:9.82 c1:0.22 c2:2.09];
float animationDistance = _targetKeyboardHeight - _initialKeyboardHeight;
float currentKeyboardHeight = _initialKeyboardHeight + animationDistance * progress;
return currentKeyboardHeight;
}
- (CGFloat)estimateClosingKeyboardHeight
{
/*
Curve parameters come from estimation: https://www.desmos.com/calculator/vhrhdaopyq
Animation takes 31 frames, which is 0.496 seconds at 60 fps.
*/
float progress = [self estimateProgressForDuration:0.496 a1:1 a2:5.65 b1:2.74 b2:8.38 c1:0.93 c2:3.29];
float currentKeyboardHeight = _initialKeyboardHeight * (1 - progress);
return currentKeyboardHeight;
}
- (float)getAnimatingKeyboardHeight
{
if (_animationStartTimestamp == 0) {
// DisplayLink animations usually start later than CAAnimations.
_animationStartTimestamp = _displayLink.targetTimestamp - _displayLink.duration;
}
CAAnimation *positionAnimation = [_measuringView.layer animationForKey:@"position"];
float caAnimationBeginTime = [[positionAnimation valueForKey:@"beginTime"] floatValue];
if (caAnimationBeginTime != 0) {
/*
CAAnimations have their own timers, and synchronizing with their timer produces
better visual effects. The CAAnimation timer is only available from the second
frame of the animation.
*/
_animationStartTimestamp = caAnimationBeginTime;
}
CGFloat keyboardHeight = 0;
if (_state == OPENING) {
keyboardHeight = [self estimateOpeningKeyboardHeight];
} else if (_state == CLOSING) {
keyboardHeight = [self estimateClosingKeyboardHeight];
}
return keyboardHeight;
}
- (float)getStaticKeyboardHeight
{
CGRect measuringFrame = _measuringView.frame;
CGFloat keyboardHeight = measuringFrame.size.height;
return keyboardHeight;
}
- (void)updateKeyboardFrame
{
CGFloat keyboardHeight = 0;
bool isKeyboardAnimationRunning = [self hasAnyAnimation:_measuringView];
if (isKeyboardAnimationRunning) {
keyboardHeight = [self getAnimatingKeyboardHeight];
} else {
// measuring view is no longer running an animation, we should settle in OPEN/CLOSE state
if (_state == OPENING || _state == CLOSING) {
_state = _state == OPENING ? OPEN : CLOSED;
}
if (_state == OPEN || _state == CLOSED) {
keyboardHeight = [self getStaticKeyboardHeight];
}
// stop display link updates if no animation is running
[[self getDisplayLink] setPaused:YES];
}
[self runListeners:keyboardHeight];
}
- (void)keyboardWillChangeFrame:(NSNotification *)notification
{
NSDictionary *userInfo = [notification userInfo];
CGRect beginFrame = [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
CGRect endFrame = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
NSTimeInterval animationDuration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
CGSize windowSize = [[[UIApplication sharedApplication] delegate] window].frame.size;
_initialKeyboardHeight = windowSize.height - beginFrame.origin.y;
_targetKeyboardHeight = windowSize.height - endFrame.origin.y;
/*
This may seem a bit confusing, but usually, the state should be either OPENED or CLOSED.
However, if it shows as OPENING, it means that the interactive dismissal was canceled.
*/
bool isInteractiveMode = _state == OPENING;
if (_targetKeyboardHeight > 0 && _state != OPEN) {
_state = OPENING;
} else if (_targetKeyboardHeight == 0 && _state != CLOSED) {
_state = CLOSING;
}
auto keyboardView = [self getKeyboardView];
bool hasKeyboardAnimation = [self hasAnyAnimation:keyboardView];
if (isInteractiveMode) {
// This condition can be met after canceling interactive dismissal.
_initialKeyboardHeight = windowSize.height - keyboardView.frame.origin.y;
}
if (hasKeyboardAnimation || isInteractiveMode) {
_measuringView.frame = CGRectMake(0, -1, 0, _initialKeyboardHeight);
[UIView animateWithDuration:animationDuration
animations:^{
self->_measuringView.frame = CGRectMake(0, -1, 0, self->_targetKeyboardHeight);
}];
[self runUpdater];
} else {
[self runListeners:_targetKeyboardHeight];
}
}
- (int)subscribeForKeyboardEvents:(KeyboardEventListenerBlock)listener
{
NSNumber *listenerId = [_nextListenerId copy];
_nextListenerId = [NSNumber numberWithInt:[_nextListenerId intValue] + 1];
RCTExecuteOnMainQueue(^() {
if (!self->_measuringView) {
self->_measuringView = [[UIView alloc] initWithFrame:CGRectMake(0, -1, 0, 0)];
UIWindow *keyWindow = [[[UIApplication sharedApplication] delegate] window];
[keyWindow addSubview:self->_measuringView];
}
if ([self->_listeners count] == 0) {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:@selector(keyboardWillChangeFrame:)
name:UIKeyboardWillChangeFrameNotification
object:nil];
[notificationCenter addObserver:self
selector:@selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification
object:nil];
[notificationCenter addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
[self->_listeners setObject:listener forKey:listenerId];
});
return [listenerId intValue];
}
- (void)unsubscribeFromKeyboardEvents:(int)listenerId
{
RCTExecuteOnMainQueue(^() {
NSNumber *_listenerId = [NSNumber numberWithInt:listenerId];
[self->_listeners removeObjectForKey:_listenerId];
if ([self->_listeners count] == 0) {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self name:UIKeyboardWillChangeFrameNotification object:nil];
[notificationCenter removeObserver:self name:UIKeyboardDidShowNotification object:nil];
[notificationCenter removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
});
}
- (void)cleanupListeners
{
RCTUnsafeExecuteOnMainQueueSync(^() {
[self->_listeners removeAllObjects];
[[self getDisplayLink] invalidate];
self->_displayLink = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
});
}
- (void)keyboardDidShow:(NSNotification *)notification
{
if (_isKeyboardObserverAttached) {
return;
}
if (auto keyboardView = [self getKeyboardView]) {
[_keyboardView addObserver:self
forKeyPath:@"center"
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
context:nil];
_isKeyboardObserverAttached = true;
}
}
- (void)keyboardWillHide:(NSNotification *)notification
{
if (_isKeyboardObserverAttached) {
[_keyboardView removeObserver:self forKeyPath:@"center"];
_isKeyboardObserverAttached = false;
}
}
- (void)updateKeyboardHeightDuringInteractiveDismiss:(CGPoint)oldKeyboardFrame
newKeyboardFrame:(CGPoint)newKeyboardFrame
{
auto keyboardView = [self getKeyboardView];
bool hasKeyboardAnimation = [self hasAnyAnimation:keyboardView];
if (hasKeyboardAnimation) {
return;
}
float windowHeight = keyboardView.window.bounds.size.height;
float keyboardHeight = keyboardView.frame.size.height;
float visibleKeyboardHeight = windowHeight - (newKeyboardFrame.y - keyboardHeight / 2);
if (oldKeyboardFrame.y > newKeyboardFrame.y) {
_state = OPENING;
} else if (oldKeyboardFrame.y < newKeyboardFrame.y) {
_state = CLOSING;
}
[self runListeners:visibleKeyboardHeight];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id> *)change
context:(void *)context
{
if ([keyPath isEqualToString:@"center"]) {
CGPoint oldKeyboardFrame = [change[NSKeyValueChangeOldKey] CGPointValue];
CGPoint newKeyboardFrame = [change[NSKeyValueChangeNewKey] CGPointValue];
[self updateKeyboardHeightDuringInteractiveDismiss:oldKeyboardFrame newKeyboardFrame:newKeyboardFrame];
}
}
- (bool)hasAnyAnimation:(REAUIView *)view
{
return view.layer.presentationLayer.animationKeys.count != 0;
;
}
- (REAUIView *_Nullable)findClass:(NSString *)className inViewsList:(NSArray<REAUIView *> *)viewList
{
for (UIWindow *view in viewList) {
if ([NSStringFromClass([view class]) isEqual:className]) {
return view;
}
}
return nil;
}
// Inspired by: https://stackoverflow.com/questions/32598490
- (REAUIView *_Nullable)getKeyboardView
{
if (_keyboardView) {
return _keyboardView;
}
NSArray<UIWindow *> *windows = [UIApplication sharedApplication].windows;
auto window = [self findClass:@"UITextEffectsWindow" inViewsList:windows];
auto keyboardContainer = [self findClass:@"UIInputSetContainerView" inViewsList:window.subviews];
_keyboardView = [self findClass:@"UIInputSetHostView" inViewsList:keyboardContainer.subviews];
return _keyboardView;
}
#endif
@end

View File

@@ -0,0 +1,24 @@
#import <Foundation/Foundation.h>
#import <RNReanimated/RNGestureHandlerStateManager.h>
#import <React/RCTUIManager.h>
#include <string>
#include <utility>
#include <vector>
namespace reanimated {
std::vector<std::pair<std::string, double>> measure(
int viewTag,
RCTUIManager *uiManager);
void scrollTo(
int scrollViewTag,
RCTUIManager *uiManager,
double x,
double y,
bool animated);
void setGestureState(
id<RNGestureHandlerStateManager> gestureHandlerStateManager,
int handlerTag,
int newState);
} // namespace reanimated

View File

@@ -0,0 +1,53 @@
#import <RNReanimated/NativeMethods.h>
#import <RNReanimated/REAUIKit.h>
#import <React/RCTEventDispatcher.h>
#import <React/RCTScrollView.h>
namespace reanimated {
std::vector<std::pair<std::string, double>> measure(int viewTag, RCTUIManager *uiManager)
{
REAUIView *view = [uiManager viewForReactTag:@(viewTag)];
REAUIView *rootView = view;
if (view == nil) {
return std::vector<std::pair<std::string, double>>(1, std::make_pair("x", -1234567.0));
}
while (rootView.superview && ![rootView isReactRootView]) {
rootView = rootView.superview;
}
if (rootView == nil) {
return std::vector<std::pair<std::string, double>>(1, std::make_pair("x", -1234567.0));
}
CGRect frame = view.frame;
CGRect globalBounds = [view convertRect:view.bounds toView:rootView];
return {
{"x", frame.origin.x},
{"y", frame.origin.y},
{"width", globalBounds.size.width},
{"height", globalBounds.size.height},
{"pageX", globalBounds.origin.x},
{"pageY", globalBounds.origin.y},
};
}
void scrollTo(int scrollViewTag, RCTUIManager *uiManager, double x, double y, bool animated)
{
REAUIView *view = [uiManager viewForReactTag:@(scrollViewTag)];
RCTScrollView *scrollView = (RCTScrollView *)view;
[scrollView scrollToOffset:(CGPoint){(CGFloat)x, (CGFloat)y} animated:animated];
}
void setGestureState(id<RNGestureHandlerStateManager> gestureHandlerStateManager, int handlerTag, int newState)
{
if (gestureHandlerStateManager != nil) {
[gestureHandlerStateManager setGestureState:newState forHandler:handlerTag];
}
}
} // namespace reanimated

View File

@@ -0,0 +1,42 @@
#if __cplusplus
#import <REAAnimationsManager.h>
#import <REAKeyboardEventObserver.h>
#import <REAModule.h>
#import <REANodesManager.h>
#import <RNReanimated/NativeReanimatedModule.h>
#import <React/RCTEventDispatcher.h>
#import <ReanimatedSensorContainer.h>
#include <memory>
namespace reanimated {
std::shared_ptr<reanimated::NativeReanimatedModule> createReanimatedModule(
RCTBridge *bridge,
const std::shared_ptr<facebook::react::CallInvoker> &jsInvoker,
const std::string &valueUnpackerCode);
#if REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
std::shared_ptr<reanimated::NativeReanimatedModule>
createReanimatedModuleBridgeless(
RCTModuleRegistry *moduleRegistry,
jsi::Runtime &runtime,
const std::string &valueUnpackerCode,
RuntimeExecutor runtimeExecutor);
#endif // REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
void commonInit(
REAModule *reaModule,
std::shared_ptr<NativeReanimatedModule> nativeReanimatedModule);
#ifdef RCT_NEW_ARCH_ENABLED
// nothing
#else // RCT_NEW_ARCH_ENABLED
void setupLayoutAnimationCallbacks(
std::shared_ptr<NativeReanimatedModule> nativeReanimatedModule,
REAAnimationsManager *animationsManager);
#endif // RCT_NEW_ARCH_ENABLED
} // namespace reanimated
#endif //__cplusplus

View File

@@ -0,0 +1,227 @@
#import <RNReanimated/LayoutAnimationsManager.h>
#import <RNReanimated/NativeMethods.h>
#import <RNReanimated/NativeProxy.h>
#import <RNReanimated/PlatformDepMethodsHolder.h>
#import <RNReanimated/PlatformDepMethodsHolderImpl.h>
#import <RNReanimated/REAAnimationsManager.h>
#import <RNReanimated/REAIOSUIScheduler.h>
#import <RNReanimated/REAJSIUtils.h>
#import <RNReanimated/REAKeyboardEventObserver.h>
#import <RNReanimated/REAMessageThread.h>
#import <RNReanimated/REAModule.h>
#import <RNReanimated/REANodesManager.h>
#import <RNReanimated/REASlowAnimations.h>
#import <RNReanimated/REASwizzledUIManager.h>
#import <RNReanimated/RNGestureHandlerStateManager.h>
#import <RNReanimated/ReanimatedRuntime.h>
#import <RNReanimated/ReanimatedSensorContainer.h>
#ifndef NDEBUG
#import <RNReanimated/REAScreensHelper.h>
#endif
#ifdef RCT_NEW_ARCH_ENABLED
#import <React/RCTBridge+Private.h>
#import <React/RCTScheduler.h>
#import <React/RCTSurfacePresenter.h>
#import <react/renderer/core/ShadowNode.h>
#import <react/renderer/uimanager/primitives.h>
#endif
#import <React/RCTUIManager.h>
#if TARGET_IPHONE_SIMULATOR
#import <dlfcn.h>
#endif
#import <RNReanimated/READisplayLink.h>
@interface RCTBridge (JSIRuntime)
- (void *)runtime;
@end
namespace reanimated {
using namespace facebook;
using namespace react;
std::shared_ptr<NativeReanimatedModule> createReanimatedModule(
RCTBridge *bridge,
const std::shared_ptr<CallInvoker> &jsInvoker,
const std::string &valueUnpackerCode)
{
REAModule *reaModule = [bridge moduleForClass:[REAModule class]];
auto nodesManager = reaModule.nodesManager;
jsi::Runtime &rnRuntime = *reinterpret_cast<facebook::jsi::Runtime *>(reaModule.bridge.runtime);
auto jsQueue = std::make_shared<REAMessageThread>([NSRunLoop currentRunLoop], ^(NSError *error) {
throw error;
});
PlatformDepMethodsHolder platformDepMethodsHolder = makePlatformDepMethodsHolder(bridge, nodesManager, reaModule);
std::shared_ptr<UIScheduler> uiScheduler = std::make_shared<REAIOSUIScheduler>();
std::shared_ptr<JSScheduler> jsScheduler = std::make_shared<JSScheduler>(rnRuntime, jsInvoker);
constexpr bool isBridgeless = false;
auto nativeReanimatedModule = std::make_shared<NativeReanimatedModule>(
rnRuntime, jsScheduler, jsQueue, uiScheduler, platformDepMethodsHolder, valueUnpackerCode, isBridgeless);
commonInit(reaModule, nativeReanimatedModule);
// Layout Animation callbacks setup
#ifdef RCT_NEW_ARCH_ENABLED
// nothing
#else
REAAnimationsManager *animationsManager = reaModule.animationsManager;
setupLayoutAnimationCallbacks(nativeReanimatedModule, animationsManager);
#endif // RCT_NEW_ARCH_ENABLED
return nativeReanimatedModule;
}
#if REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
std::shared_ptr<NativeReanimatedModule> createReanimatedModuleBridgeless(
RCTModuleRegistry *moduleRegistry,
jsi::Runtime &runtime,
const std::string &valueUnpackerCode,
RuntimeExecutor runtimeExecutor)
{
REAModule *reaModule = [moduleRegistry moduleForName:"ReanimatedModule"];
auto nodesManager = reaModule.nodesManager;
auto jsQueue = std::make_shared<REAMessageThread>([NSRunLoop currentRunLoop], ^(NSError *error) {
throw error;
});
PlatformDepMethodsHolder platformDepMethodsHolder =
makePlatformDepMethodsHolderBridgeless(moduleRegistry, nodesManager, reaModule);
auto uiScheduler = std::make_shared<REAIOSUIScheduler>();
auto jsScheduler = std::make_shared<JSScheduler>(runtime, runtimeExecutor);
constexpr bool isBridgeless = true;
auto nativeReanimatedModule = std::make_shared<NativeReanimatedModule>(
runtime, jsScheduler, jsQueue, uiScheduler, platformDepMethodsHolder, valueUnpackerCode, isBridgeless);
commonInit(reaModule, nativeReanimatedModule);
return nativeReanimatedModule;
}
#endif // REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
void commonInit(REAModule *reaModule, std::shared_ptr<NativeReanimatedModule> nativeReanimatedModule)
{
[reaModule.nodesManager registerEventHandler:^(id<RCTEvent> event) {
// handles RCTEvents from RNGestureHandler
std::string eventName = [event.eventName UTF8String];
int emitterReactTag = [event.viewTag intValue];
id eventData = [event arguments][2];
jsi::Runtime &uiRuntime = nativeReanimatedModule->getUIRuntime();
jsi::Value payload = convertObjCObjectToJSIValue(uiRuntime, eventData);
double currentTime = CACurrentMediaTime() * 1000;
nativeReanimatedModule->handleEvent(eventName, emitterReactTag, payload, currentTime);
}];
#ifdef RCT_NEW_ARCH_ENABLED
std::weak_ptr<NativeReanimatedModule> weakNativeReanimatedModule = nativeReanimatedModule; // to avoid retain cycle
[reaModule.nodesManager registerPerformOperations:^() {
if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) {
nativeReanimatedModule->performOperations();
}
}];
#endif // RCT_NEW_ARCH_ENABLED
}
#ifdef RCT_NEW_ARCH_ENABLED
// nothing
#else // RCT_NEW_ARCH_ENABLED
void setupLayoutAnimationCallbacks(
std::shared_ptr<NativeReanimatedModule> nativeReanimatedModule,
REAAnimationsManager *animationsManager)
{
std::weak_ptr<NativeReanimatedModule> weakNativeReanimatedModule = nativeReanimatedModule; // to avoid retain cycle
[animationsManager
setAnimationStartingBlock:^(NSNumber *_Nonnull tag, LayoutAnimationType type, NSDictionary *_Nonnull values) {
if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) {
jsi::Runtime &rt = nativeReanimatedModule->getUIRuntime();
jsi::Object yogaValues(rt);
for (NSString *key in values.allKeys) {
NSObject *value = values[key];
if ([values[key] isKindOfClass:[NSArray class]]) {
NSArray *transformArray = (NSArray *)value;
jsi::Array matrix(rt, 9);
for (int i = 0; i < 9; i++) {
matrix.setValueAtIndex(rt, i, [(NSNumber *)transformArray[i] doubleValue]);
}
yogaValues.setProperty(rt, [key UTF8String], matrix);
} else {
yogaValues.setProperty(rt, [key UTF8String], [(NSNumber *)value doubleValue]);
}
}
nativeReanimatedModule->layoutAnimationsManager().startLayoutAnimation(rt, [tag intValue], type, yogaValues);
}
}];
[animationsManager setHasAnimationBlock:^(NSNumber *_Nonnull tag, LayoutAnimationType type) {
if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) {
bool hasLayoutAnimation =
nativeReanimatedModule->layoutAnimationsManager().hasLayoutAnimation([tag intValue], type);
return hasLayoutAnimation ? YES : NO;
}
return NO;
}];
[animationsManager setShouldAnimateExitingBlock:^(NSNumber *_Nonnull tag, BOOL shouldAnimate) {
if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) {
bool shouldAnimateExiting =
nativeReanimatedModule->layoutAnimationsManager().shouldAnimateExiting([tag intValue], shouldAnimate);
return shouldAnimateExiting ? YES : NO;
}
return NO;
}];
[animationsManager setAnimationRemovingBlock:^(NSNumber *_Nonnull tag) {
if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) {
nativeReanimatedModule->layoutAnimationsManager().clearLayoutAnimationConfig([tag intValue]);
}
}];
[animationsManager setSharedTransitionRemovingBlock:^(NSNumber *_Nonnull tag) {
if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) {
nativeReanimatedModule->layoutAnimationsManager().clearSharedTransitionConfig([tag intValue]);
}
}];
[animationsManager setCancelAnimationBlock:^(NSNumber *_Nonnull tag) {
if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) {
jsi::Runtime &rt = nativeReanimatedModule->getUIRuntime();
nativeReanimatedModule->layoutAnimationsManager().cancelLayoutAnimation(rt, [tag intValue]);
}
}];
[animationsManager setFindPrecedingViewTagForTransitionBlock:^NSNumber *_Nullable(NSNumber *_Nonnull tag) {
if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) {
int resultTag =
nativeReanimatedModule->layoutAnimationsManager().findPrecedingViewTagForTransition([tag intValue]);
return resultTag == -1 ? nil : @(resultTag);
}
return nil;
}];
#ifndef NDEBUG
[animationsManager setCheckDuplicateSharedTagBlock:^(REAUIView *view, NSNumber *_Nonnull viewTag) {
if (auto nativeReanimatedModule = weakNativeReanimatedModule.lock()) {
REAUIView *screen = [REAScreensHelper getScreenForView:(REAUIView *)view];
auto screenTag = [screen.reactTag intValue];
// Here we check if there are duplicate tags (we don't use return bool value currently)
nativeReanimatedModule->layoutAnimationsManager().checkDuplicateSharedTag([viewTag intValue], screenTag);
}
}];
#endif // NDEBUG
}
#endif // RCT_NEW_ARCH_ENABLED
} // namespace reanimated

View File

@@ -0,0 +1,59 @@
#if __cplusplus
#import <REAAnimationsManager.h>
#import <REAKeyboardEventObserver.h>
#import <REAModule.h>
#import <REANodesManager.h>
#import <RNReanimated/NativeReanimatedModule.h>
#import <React/RCTEventDispatcher.h>
#import <ReanimatedSensorContainer.h>
#include <memory>
namespace reanimated {
PlatformDepMethodsHolder makePlatformDepMethodsHolder(
RCTBridge *bridge,
REANodesManager *nodesManager,
REAModule *reaModule);
#if REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
PlatformDepMethodsHolder makePlatformDepMethodsHolderBridgeless(
RCTModuleRegistry *moduleRegistry,
REANodesManager *nodesManager,
REAModule *reaModule);
SetGestureStateFunction makeSetGestureStateFunctionBridgeless(
RCTModuleRegistry *moduleRegistry);
#endif // REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
#ifdef RCT_NEW_ARCH_ENABLED
SynchronouslyUpdateUIPropsFunction makeSynchronouslyUpdateUIPropsFunction(
REANodesManager *nodesManager);
#else // RCT_NEW_ARCH_ENABLED
UpdatePropsFunction makeUpdatePropsFunction(REAModule *reaModule);
MeasureFunction makeMeasureFunction(RCTUIManager *uiManager);
ScrollToFunction makeScrollToFunction(RCTUIManager *uiManager);
DispatchCommandFunction makeDispatchCommandFunction(RCTUIManager *uiManager);
ConfigurePropsFunction makeConfigurePropsFunction(REAModule *reaModule);
ObtainPropFunction makeObtainPropFunction(REAModule *reaModule);
#endif // RCT_NEW_ARCH_ENABLED
SetGestureStateFunction makeSetGestureStateFunction(RCTBridge *bridge);
RequestRenderFunction makeRequestRender(REANodesManager *nodesManager);
GetAnimationTimestampFunction makeGetAnimationTimestamp();
ProgressLayoutAnimationFunction makeProgressLayoutAnimation(
REAModule *reaModule);
EndLayoutAnimationFunction makeEndLayoutAnimation(REAModule *reaModule);
MaybeFlushUIUpdatesQueueFunction makeMaybeFlushUIUpdatesQueueFunction(
REANodesManager *nodesManager);
RegisterSensorFunction makeRegisterSensorFunction(
ReanimatedSensorContainer *reanimatedSensorContainer);
UnregisterSensorFunction makeUnregisterSensorFunction(
ReanimatedSensorContainer *reanimatedSensorContainer);
KeyboardEventSubscribeFunction makeSubscribeForKeyboardEventsFunction(
REAKeyboardEventObserver *keyboardObserver);
KeyboardEventUnsubscribeFunction makeUnsubscribeFromKeyboardEventsFunction(
REAKeyboardEventObserver *keyboardObserver);
} // namespace reanimated
#endif //__cplusplus

View File

@@ -0,0 +1,429 @@
#import <RNReanimated/LayoutAnimationsManager.h>
#import <RNReanimated/NativeMethods.h>
#import <RNReanimated/NativeProxy.h>
#import <RNReanimated/PlatformDepMethodsHolder.h>
#import <RNReanimated/REAAnimationsManager.h>
#import <RNReanimated/REAIOSUIScheduler.h>
#import <RNReanimated/REAJSIUtils.h>
#import <RNReanimated/REAKeyboardEventObserver.h>
#import <RNReanimated/REAMessageThread.h>
#import <RNReanimated/REAModule.h>
#import <RNReanimated/REANodesManager.h>
#import <RNReanimated/REASlowAnimations.h>
#import <RNReanimated/REASwizzledUIManager.h>
#import <RNReanimated/RNGestureHandlerStateManager.h>
#import <RNReanimated/ReanimatedRuntime.h>
#import <RNReanimated/ReanimatedSensorContainer.h>
#ifndef NDEBUG
#import <RNReanimated/REAScreensHelper.h>
#endif
#ifdef RCT_NEW_ARCH_ENABLED
#import <React/RCTBridge+Private.h>
#import <React/RCTScheduler.h>
#import <React/RCTSurfacePresenter.h>
#import <react/renderer/core/ShadowNode.h>
#import <react/renderer/uimanager/primitives.h>
#endif
#import <React/RCTUIManager.h>
#if TARGET_IPHONE_SIMULATOR
#import <dlfcn.h>
#endif
#import <RNReanimated/READisplayLink.h>
@interface RCTUIManager (DispatchCommand)
- (void)dispatchViewManagerCommand:(nonnull NSNumber *)reactTag
commandID:(id /*(NSString or NSNumber) */)commandID
commandArgs:(NSArray<id> *)commandArgs;
@end
namespace reanimated {
using namespace facebook;
using namespace react;
static NSSet *convertProps(jsi::Runtime &rt, const jsi::Value &props)
{
NSMutableSet *propsSet = [[NSMutableSet alloc] init];
jsi::Array propsNames = props.asObject(rt).asArray(rt);
for (int i = 0; i < propsNames.size(rt); i++) {
NSString *propName = @(propsNames.getValueAtIndex(rt, i).asString(rt).utf8(rt).c_str());
[propsSet addObject:propName];
}
return propsSet;
}
SetGestureStateFunction makeSetGestureStateFunction(RCTBridge *bridge)
{
id<RNGestureHandlerStateManager> gestureHandlerStateManager = nil;
auto setGestureStateFunction = [gestureHandlerStateManager, bridge](int handlerTag, int newState) mutable {
if (gestureHandlerStateManager == nil) {
gestureHandlerStateManager = [bridge moduleForName:@"RNGestureHandlerModule"];
}
setGestureState(gestureHandlerStateManager, handlerTag, newState);
};
return setGestureStateFunction;
}
#if REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
SetGestureStateFunction makeSetGestureStateFunctionBridgeless(RCTModuleRegistry *moduleRegistry)
{
id<RNGestureHandlerStateManager> gestureHandlerStateManager = nil;
auto setGestureStateFunction = [gestureHandlerStateManager, moduleRegistry](int handlerTag, int newState) mutable {
if (gestureHandlerStateManager == nil) {
gestureHandlerStateManager = [moduleRegistry moduleForName:"RNGestureHandlerModule"];
}
setGestureState(gestureHandlerStateManager, handlerTag, newState);
};
return setGestureStateFunction;
}
#endif // REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
RequestRenderFunction makeRequestRender(REANodesManager *nodesManager)
{
auto requestRender = [nodesManager](std::function<void(double)> onRender, jsi::Runtime &rt) {
[nodesManager postOnAnimation:^(READisplayLink *displayLink) {
#if !TARGET_OS_OSX
auto targetTimestamp = displayLink.targetTimestamp;
#else
// TODO macOS targetTimestamp isn't available on macOS
auto targetTimestamp = displayLink.timestamp + displayLink.duration;
#endif
double frameTimestamp =
(targetTimestamp)*1000;
onRender(frameTimestamp);
}];
};
return requestRender;
}
#ifdef RCT_NEW_ARCH_ENABLED
SynchronouslyUpdateUIPropsFunction makeSynchronouslyUpdateUIPropsFunction(REANodesManager *nodesManager)
{
auto synchronouslyUpdateUIPropsFunction = [nodesManager](jsi::Runtime &rt, Tag tag, const jsi::Object &props) {
NSNumber *viewTag = @(tag);
NSDictionary *uiProps = convertJSIObjectToNSDictionary(rt, props);
[nodesManager synchronouslyUpdateViewOnUIThread:viewTag props:uiProps];
};
return synchronouslyUpdateUIPropsFunction;
}
#endif // RCT_NEW_ARCH_ENABLED
#ifdef RCT_NEW_ARCH_ENABLED
// nothing
#else // RCT_NEW_ARCH_ENABLED
UpdatePropsFunction makeUpdatePropsFunction(REAModule *reaModule)
{
auto updatePropsFunction = [reaModule](jsi::Runtime &rt, const jsi::Value &operations) -> void {
auto array = operations.asObject(rt).asArray(rt);
size_t length = array.size(rt);
for (size_t i = 0; i < length; ++i) {
auto item = array.getValueAtIndex(rt, i).asObject(rt);
int viewTag = item.getProperty(rt, "tag").asNumber();
const jsi::Value &viewName = item.getProperty(rt, "name");
const jsi::Object &props = item.getProperty(rt, "updates").asObject(rt);
NSString *nsViewName = [NSString stringWithCString:viewName.asString(rt).utf8(rt).c_str()
encoding:[NSString defaultCStringEncoding]];
NSDictionary *propsDict = convertJSIObjectToNSDictionary(rt, props);
[reaModule.nodesManager updateProps:propsDict ofViewWithTag:[NSNumber numberWithInt:viewTag] withName:nsViewName];
}
};
return updatePropsFunction;
}
MeasureFunction makeMeasureFunction(RCTUIManager *uiManager)
{
auto measureFunction = [uiManager](int viewTag) -> std::vector<std::pair<std::string, double>> {
return measure(viewTag, uiManager);
};
return measureFunction;
}
ScrollToFunction makeScrollToFunction(RCTUIManager *uiManager)
{
auto scrollToFunction = [uiManager](int viewTag, double x, double y, bool animated) {
scrollTo(viewTag, uiManager, x, y, animated);
};
return scrollToFunction;
}
DispatchCommandFunction makeDispatchCommandFunction(RCTUIManager *uiManager)
{
auto dispatchCommandFunction =
[uiManager](
jsi::Runtime &rt, const int tag, const jsi::Value &commandNameValue, const jsi::Value &argsValue) -> void {
NSNumber *viewTag = [NSNumber numberWithInt:tag];
NSString *commandID = [NSString stringWithCString:commandNameValue.asString(rt).utf8(rt).c_str()
encoding:[NSString defaultCStringEncoding]];
NSArray *commandArgs = convertJSIArrayToNSArray(rt, argsValue.asObject(rt).asArray(rt));
RCTExecuteOnUIManagerQueue(^{
[uiManager dispatchViewManagerCommand:viewTag commandID:commandID commandArgs:commandArgs];
});
};
return dispatchCommandFunction;
}
ConfigurePropsFunction makeConfigurePropsFunction(REAModule *reaModule)
{
auto configurePropsFunction = [reaModule](
jsi::Runtime &rt, const jsi::Value &uiProps, const jsi::Value &nativeProps) {
NSSet *uiPropsSet = convertProps(rt, uiProps);
NSSet *nativePropsSet = convertProps(rt, nativeProps);
[reaModule.nodesManager configureUiProps:uiPropsSet andNativeProps:nativePropsSet];
};
return configurePropsFunction;
}
ObtainPropFunction makeObtainPropFunction(REAModule *reaModule)
{
auto obtainPropFunction = [reaModule](jsi::Runtime &rt, const int viewTag, const jsi::Value &propName) -> jsi::Value {
NSString *propNameConverted = [NSString stringWithFormat:@"%s", propName.asString(rt).utf8(rt).c_str()];
std::string resultStr = std::string([[reaModule.nodesManager obtainProp:[NSNumber numberWithInt:viewTag]
propName:propNameConverted] UTF8String]);
jsi::Value val = jsi::String::createFromUtf8(rt, resultStr);
return val;
};
return obtainPropFunction;
}
#endif // RCT_NEW_ARCH_ENABLED
GetAnimationTimestampFunction makeGetAnimationTimestamp()
{
auto getAnimationTimestamp = []() { return calculateTimestampWithSlowAnimations(CACurrentMediaTime()) * 1000; };
return getAnimationTimestamp;
}
ProgressLayoutAnimationFunction makeProgressLayoutAnimation(REAModule *reaModule)
{
#ifdef RCT_NEW_ARCH_ENABLED
auto progressLayoutAnimation = [=](jsi::Runtime &rt, int tag, const jsi::Object &newStyle, bool isSharedTransition) {
// noop
};
#else // RCT_NEW_ARCH_ENABLED
REAAnimationsManager *animationsManager = reaModule.animationsManager;
__weak REAAnimationsManager *weakAnimationsManager = animationsManager;
auto progressLayoutAnimation = [=](jsi::Runtime &rt, int tag, const jsi::Object &newStyle, bool isSharedTransition) {
NSDictionary *propsDict = convertJSIObjectToNSDictionary(rt, newStyle);
[weakAnimationsManager progressLayoutAnimationWithStyle:propsDict
forTag:@(tag)
isSharedTransition:isSharedTransition];
};
#endif // RCT_NEW_ARCH_ENABLED
return progressLayoutAnimation;
}
EndLayoutAnimationFunction makeEndLayoutAnimation(REAModule *reaModule)
{
#ifdef RCT_NEW_ARCH_ENABLED
auto endLayoutAnimation = [=](int tag, bool removeView) {
// noop
};
#else // RCT_NEW_ARCH_ENABLED
REAAnimationsManager *animationsManager = reaModule.animationsManager;
__weak REAAnimationsManager *weakAnimationsManager = animationsManager;
auto endLayoutAnimation = [=](int tag, bool removeView) {
[weakAnimationsManager endLayoutAnimationForTag:@(tag) removeView:removeView];
};
#endif // RCT_NEW_ARCH_ENABLED
return endLayoutAnimation;
}
MaybeFlushUIUpdatesQueueFunction makeMaybeFlushUIUpdatesQueueFunction(REANodesManager *nodesManager)
{
auto maybeFlushUIUpdatesQueueFunction = [nodesManager]() { [nodesManager maybeFlushUIUpdatesQueue]; };
return maybeFlushUIUpdatesQueueFunction;
}
RegisterSensorFunction makeRegisterSensorFunction(ReanimatedSensorContainer *reanimatedSensorContainer)
{
auto registerSensorFunction =
[=](int sensorType, int interval, int iosReferenceFrame, std::function<void(double[], int)> setter) -> int {
return [reanimatedSensorContainer registerSensor:(ReanimatedSensorType)sensorType
interval:interval
iosReferenceFrame:iosReferenceFrame
setter:^(double *data, int orientationDegrees) {
setter(data, orientationDegrees);
}];
};
return registerSensorFunction;
}
UnregisterSensorFunction makeUnregisterSensorFunction(ReanimatedSensorContainer *reanimatedSensorContainer)
{
auto unregisterSensorFunction = [=](int sensorId) { [reanimatedSensorContainer unregisterSensor:sensorId]; };
return unregisterSensorFunction;
}
KeyboardEventSubscribeFunction makeSubscribeForKeyboardEventsFunction(REAKeyboardEventObserver *keyboardObserver)
{
auto subscribeForKeyboardEventsFunction =
[=](std::function<void(int keyboardState, int height)> keyboardEventDataUpdater, bool isStatusBarTranslucent) {
// ignore isStatusBarTranslucent - it's Android only
return [keyboardObserver subscribeForKeyboardEvents:^(int keyboardState, int height) {
keyboardEventDataUpdater(keyboardState, height);
}];
};
return subscribeForKeyboardEventsFunction;
}
KeyboardEventUnsubscribeFunction makeUnsubscribeFromKeyboardEventsFunction(REAKeyboardEventObserver *keyboardObserver)
{
auto unsubscribeFromKeyboardEventsFunction = [=](int listenerId) {
[keyboardObserver unsubscribeFromKeyboardEvents:listenerId];
};
return unsubscribeFromKeyboardEventsFunction;
}
PlatformDepMethodsHolder
makePlatformDepMethodsHolder(RCTBridge *bridge, REANodesManager *nodesManager, REAModule *reaModule)
{
auto requestRender = makeRequestRender(nodesManager);
#ifdef RCT_NEW_ARCH_ENABLED
auto synchronouslyUpdateUIPropsFunction = makeSynchronouslyUpdateUIPropsFunction(nodesManager);
#endif // RCT_NEW_ARCH_ENABLED
#ifdef RCT_NEW_ARCH_ENABLED
// nothing
#else
RCTUIManager *uiManager = nodesManager.uiManager;
auto updatePropsFunction = makeUpdatePropsFunction(reaModule);
auto measureFunction = makeMeasureFunction(uiManager);
auto scrollToFunction = makeScrollToFunction(uiManager);
auto dispatchCommandFunction = makeDispatchCommandFunction(uiManager);
#endif // RCT_NEW_ARCH_ENABLED
auto getAnimationTimestamp = makeGetAnimationTimestamp();
auto setGestureStateFunction = makeSetGestureStateFunction(bridge);
#ifdef RCT_NEW_ARCH_ENABLED
auto progressLayoutAnimation = makeProgressLayoutAnimation(reaModule);
auto endLayoutAnimation = makeEndLayoutAnimation(reaModule);
#else
// Layout Animations start
auto progressLayoutAnimation = makeProgressLayoutAnimation(reaModule);
auto endLayoutAnimation = makeEndLayoutAnimation(reaModule);
auto configurePropsFunction = makeConfigurePropsFunction(reaModule);
// Layout Animations end
#endif
auto maybeFlushUIUpdatesQueueFunction = makeMaybeFlushUIUpdatesQueueFunction(nodesManager);
ReanimatedSensorContainer *reanimatedSensorContainer = [[ReanimatedSensorContainer alloc] init];
auto registerSensorFunction = makeRegisterSensorFunction(reanimatedSensorContainer);
auto unregisterSensorFunction = makeUnregisterSensorFunction(reanimatedSensorContainer);
REAKeyboardEventObserver *keyboardObserver = [[REAKeyboardEventObserver alloc] init];
auto subscribeForKeyboardEventsFunction = makeSubscribeForKeyboardEventsFunction(keyboardObserver);
auto unsubscribeFromKeyboardEventsFunction = makeUnsubscribeFromKeyboardEventsFunction(keyboardObserver);
// end keyboard events
#ifdef RCT_NEW_ARCH_ENABLED
// nothing
#else
auto obtainPropFunction = makeObtainPropFunction(reaModule);
#endif
PlatformDepMethodsHolder platformDepMethodsHolder = {
requestRender,
#ifdef RCT_NEW_ARCH_ENABLED
synchronouslyUpdateUIPropsFunction,
#else
updatePropsFunction,
scrollToFunction,
dispatchCommandFunction,
measureFunction,
configurePropsFunction,
obtainPropFunction,
#endif
getAnimationTimestamp,
progressLayoutAnimation,
endLayoutAnimation,
registerSensorFunction,
unregisterSensorFunction,
setGestureStateFunction,
subscribeForKeyboardEventsFunction,
unsubscribeFromKeyboardEventsFunction,
maybeFlushUIUpdatesQueueFunction,
};
return platformDepMethodsHolder;
}
#if REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
PlatformDepMethodsHolder makePlatformDepMethodsHolderBridgeless(
RCTModuleRegistry *moduleRegistry,
REANodesManager *nodesManager,
REAModule *reaModule)
{
auto requestRender = makeRequestRender(nodesManager);
auto synchronouslyUpdateUIPropsFunction = makeSynchronouslyUpdateUIPropsFunction(nodesManager);
auto getAnimationTimestamp = makeGetAnimationTimestamp();
auto progressLayoutAnimation = makeProgressLayoutAnimation(reaModule);
auto endLayoutAnimation = makeEndLayoutAnimation(reaModule);
ReanimatedSensorContainer *reanimatedSensorContainer = [[ReanimatedSensorContainer alloc] init];
auto registerSensorFunction = makeRegisterSensorFunction(reanimatedSensorContainer);
auto unregisterSensorFunction = makeUnregisterSensorFunction(reanimatedSensorContainer);
auto setGestureStateFunction = makeSetGestureStateFunctionBridgeless(moduleRegistry);
REAKeyboardEventObserver *keyboardObserver = [[REAKeyboardEventObserver alloc] init];
auto subscribeForKeyboardEventsFunction = makeSubscribeForKeyboardEventsFunction(keyboardObserver);
auto unsubscribeFromKeyboardEventsFunction = makeUnsubscribeFromKeyboardEventsFunction(keyboardObserver);
auto maybeFlushUIUpdatesQueueFunction = makeMaybeFlushUIUpdatesQueueFunction(nodesManager);
PlatformDepMethodsHolder platformDepMethodsHolder = {
requestRender,
synchronouslyUpdateUIPropsFunction,
getAnimationTimestamp,
progressLayoutAnimation,
endLayoutAnimation,
registerSensorFunction,
unregisterSensorFunction,
setGestureStateFunction,
subscribeForKeyboardEventsFunction,
unsubscribeFromKeyboardEventsFunction,
maybeFlushUIUpdatesQueueFunction,
};
return platformDepMethodsHolder;
}
#endif // REACT_NATIVE_MINOR_VERSION >= 74 && defined(RCT_NEW_ARCH_ENABLED)
} // namespace reanimated

View File

@@ -0,0 +1,15 @@
#import <RNReanimated/ReanimatedHiddenHeaders.h>
#include <stdio.h>
namespace reanimated {
class REAIOSLogger : public LoggerInterface {
public:
void log(const char *str) override;
void log(const std::string &str) override;
void log(double d) override;
void log(int i) override;
void log(bool b) override;
};
} // namespace reanimated

View File

@@ -0,0 +1,33 @@
#import <Foundation/Foundation.h>
#import <RNReanimated/REAIOSLogger.h>
namespace reanimated {
std::unique_ptr<LoggerInterface> Logger::instance = std::make_unique<REAIOSLogger>();
void REAIOSLogger::log(const char *str)
{
NSLog(@"%@", [NSString stringWithCString:str encoding:[NSString defaultCStringEncoding]]);
}
void REAIOSLogger::log(const std::string &str)
{
log(str.c_str());
}
void REAIOSLogger::log(double d)
{
NSLog(@"%lf", d);
}
void REAIOSLogger::log(int i)
{
NSLog(@"%i", i);
}
void REAIOSLogger::log(bool b)
{
log(b ? "true" : "false");
}
} // namespace reanimated

View File

@@ -0,0 +1,17 @@
#import <RNReanimated/UIScheduler.h>
#import <React/RCTUIManager.h>
#import <ReactCommon/CallInvoker.h>
#include <memory>
namespace reanimated {
using namespace facebook;
using namespace react;
class REAIOSUIScheduler : public UIScheduler {
public:
void scheduleOnUI(std::function<void()> job) override;
};
} // namespace reanimated

View File

@@ -0,0 +1,24 @@
#import <RNReanimated/REAIOSUIScheduler.h>
namespace reanimated {
using namespace facebook;
using namespace react;
void REAIOSUIScheduler::scheduleOnUI(std::function<void()> job)
{
if ([NSThread isMainThread]) {
job();
return;
}
UIScheduler::scheduleOnUI(job);
if (!scheduledOnUI_) {
dispatch_async(dispatch_get_main_queue(), ^{
triggerUI();
});
}
}
} // namespace reanimated

View File

@@ -0,0 +1,38 @@
/*
Note: Files REAInitializer.h and REAInitializer.m are deprecated and will
be removed in future releases. They are currently kept for backward
compatibility and will be retained for a few upcoming releases.
*/
#ifndef RCT_NEW_ARCH_ENABLED
#import <Foundation/Foundation.h>
#import <React/RCTBridge.h>
#if REACT_NATIVE_MINOR_VERSION <= 71
#import <React/RCTJSIExecutorRuntimeInstaller.h>
using namespace facebook::react;
#endif // REACT_NATIVE_MINOR_VERSION <= 71
NS_ASSUME_NONNULL_BEGIN
namespace reanimated {
[[deprecated(
"REAInitializer method is no longer required, you can just remove invocation.")]] void
REAInitializer(RCTBridge *bridge);
#if REACT_NATIVE_MINOR_VERSION <= 71
[[deprecated(
"REAJSIExecutorRuntimeInstaller method is no longer required, you can just remove invocation.")]] JSIExecutor::
RuntimeInstaller
REAJSIExecutorRuntimeInstaller(
RCTBridge *bridge,
JSIExecutor::RuntimeInstaller runtimeInstallerToWrap);
#endif // REACT_NATIVE_MINOR_VERSION <= 71
} // namespace reanimated
NS_ASSUME_NONNULL_END
#endif // RCT_NEW_ARCH_ENABLED

View File

@@ -0,0 +1,30 @@
#ifndef RCT_NEW_ARCH_ENABLED
#import <RNReanimated/REAInitializer.h>
namespace reanimated {
void REAInitializer(RCTBridge *bridge)
{
// do nothing, just for backward compatibility
}
#if REACT_NATIVE_MINOR_VERSION <= 71
JSIExecutor::RuntimeInstaller REAJSIExecutorRuntimeInstaller(
RCTBridge *bridge,
JSIExecutor::RuntimeInstaller runtimeInstallerToWrap)
{
const auto runtimeInstaller = [runtimeInstallerToWrap](facebook::jsi::Runtime &runtime) {
if (runtimeInstallerToWrap) {
runtimeInstallerToWrap(runtime);
}
};
return runtimeInstaller;
}
#endif // REACT_NATIVE_MINOR_VERSION <= 71
} // namespace reanimated
#endif // RCT_NEW_ARCH_ENABLED

View File

@@ -0,0 +1,124 @@
#pragma once
#include <jsi/jsi.h>
using namespace facebook;
// Copied from RCTTurboModule.mm
/**
* All static helper functions are ObjC++ specific.
*/
static jsi::Value convertNSNumberToJSIBoolean(jsi::Runtime &runtime, NSNumber *value)
{
return jsi::Value((bool)[value boolValue]);
}
static jsi::Value convertNSNumberToJSINumber(jsi::Runtime &runtime, NSNumber *value)
{
return jsi::Value([value doubleValue]);
}
static jsi::String convertNSStringToJSIString(jsi::Runtime &runtime, NSString *value)
{
return jsi::String::createFromUtf8(runtime, [value UTF8String] ?: "");
}
static jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value);
static jsi::Object convertNSDictionaryToJSIObject(jsi::Runtime &runtime, NSDictionary *value)
{
jsi::Object result = jsi::Object(runtime);
for (NSString *k in value) {
result.setProperty(runtime, [k UTF8String], convertObjCObjectToJSIValue(runtime, value[k]));
}
return result;
}
static jsi::Array convertNSArrayToJSIArray(jsi::Runtime &runtime, NSArray *value)
{
jsi::Array result = jsi::Array(runtime, value.count);
for (size_t i = 0; i < value.count; i++) {
result.setValueAtIndex(runtime, i, convertObjCObjectToJSIValue(runtime, value[i]));
}
return result;
}
static jsi::Value convertObjCObjectToJSIValue(jsi::Runtime &runtime, id value)
{
if ([value isKindOfClass:[NSString class]]) {
return convertNSStringToJSIString(runtime, (NSString *)value);
} else if ([value isKindOfClass:[NSNumber class]]) {
if ([value isKindOfClass:[@YES class]]) {
return convertNSNumberToJSIBoolean(runtime, (NSNumber *)value);
}
return convertNSNumberToJSINumber(runtime, (NSNumber *)value);
} else if ([value isKindOfClass:[NSDictionary class]]) {
return convertNSDictionaryToJSIObject(runtime, (NSDictionary *)value);
} else if ([value isKindOfClass:[NSArray class]]) {
return convertNSArrayToJSIArray(runtime, (NSArray *)value);
} else if (value == (id)kCFNull) {
return jsi::Value::null();
}
return jsi::Value::undefined();
}
// Other
static id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value);
static NSString *convertJSIStringToNSString(jsi::Runtime &runtime, const jsi::String &value)
{
return [NSString stringWithUTF8String:value.utf8(runtime).c_str()];
}
static NSDictionary *convertJSIObjectToNSDictionary(jsi::Runtime &runtime, const jsi::Object &value)
{
jsi::Array propertyNames = value.getPropertyNames(runtime);
size_t size = propertyNames.size(runtime);
NSMutableDictionary *result = [NSMutableDictionary new];
for (size_t i = 0; i < size; i++) {
jsi::String name = propertyNames.getValueAtIndex(runtime, i).getString(runtime);
NSString *k = convertJSIStringToNSString(runtime, name);
id v = convertJSIValueToObjCObject(runtime, value.getProperty(runtime, name));
if (v) {
result[k] = v;
}
}
return [result copy];
}
static NSArray *convertJSIArrayToNSArray(jsi::Runtime &runtime, const jsi::Array &value)
{
size_t size = value.size(runtime);
NSMutableArray *result = [NSMutableArray new];
for (size_t i = 0; i < size; i++) {
// Insert kCFNull when it's `undefined` value to preserve the indices.
[result addObject:convertJSIValueToObjCObject(runtime, value.getValueAtIndex(runtime, i)) ?: (id)kCFNull];
}
return [result copy];
}
static id convertJSIValueToObjCObject(jsi::Runtime &runtime, const jsi::Value &value)
{
if (value.isUndefined() || value.isNull()) {
return nil;
}
if (value.isBool()) {
return @(value.getBool());
}
if (value.isNumber()) {
return @(value.getNumber());
}
if (value.isString()) {
return convertJSIStringToNSString(runtime, value.getString(runtime));
}
if (value.isObject()) {
jsi::Object o = value.getObject(runtime);
if (o.isArray(runtime)) {
return convertJSIArrayToNSArray(runtime, o.getArray(runtime));
}
return convertJSIObjectToNSDictionary(runtime, o);
}
throw std::runtime_error("[Reanimated] Unsupported jsi::Value kind.");
}

View File

@@ -0,0 +1,22 @@
#pragma once
#import <memory>
#import <string>
#import <Foundation/Foundation.h>
#import <React/RCTJavaScriptExecutor.h>
#import <React/RCTMessageThread.h>
#import <cxxreact/MessageQueueThread.h>
namespace facebook {
namespace react {
class REAMessageThread : public RCTMessageThread {
public:
using RCTMessageThread::RCTMessageThread;
virtual void quitSynchronous() override;
};
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,38 @@
#import <RNReanimated/REAMessageThread.h>
#include <condition_variable>
#include <mutex>
#import <React/RCTCxxUtils.h>
#import <React/RCTMessageThread.h>
#import <React/RCTUtils.h>
namespace facebook {
namespace react {
// Essentially the same as RCTMessageThread, but with public fields.
struct REAMessageThreadPublic {
// I don't know why we need three vtables (if you know then feel free to
// explain it instead of this message), but this is what makes the casts in
// quitSynchronous() work correctly.
void *vtable1;
void *vtable2;
void *vtable3;
CFRunLoopRef m_cfRunLoop;
RCTJavaScriptCompleteBlock m_errorBlock;
std::atomic_bool m_shutdown;
};
// We need to prevent any new code from being executed on the thread as there
// is an assertion for that in the destructor of RCTMessageThread, but we have
// to override quitSynchronous() as it would quit the main looper and freeze
// the app.
void REAMessageThread::quitSynchronous()
{
RCTMessageThread *rctThread = static_cast<RCTMessageThread *>(this);
REAMessageThreadPublic *rctThreadPublic = reinterpret_cast<REAMessageThreadPublic *>(rctThread);
rctThreadPublic->m_shutdown = true;
}
} // namespace react
} // namespace facebook

View File

@@ -0,0 +1,29 @@
#if !TARGET_OS_TV && !TARGET_OS_OSX
#import <CoreMotion/CoreMotion.h>
#endif
#import <RNReanimated/ReanimatedSensorType.h>
@interface ReanimatedSensor : NSObject {
ReanimatedSensorType _sensorType;
double _interval;
double _lastTimestamp;
int _referenceFrame;
#if !TARGET_OS_TV && !TARGET_OS_OSX
CMMotionManager *_motionManager;
#endif
void (^_setter)(double[], int);
}
- (instancetype)init:(ReanimatedSensorType)sensorType
interval:(int)interval
iosReferenceFrame:(int)iosReferenceFrame
setter:(void (^)(double[], int))setter;
- (bool)initialize;
- (bool)initializeGyroscope;
- (bool)initializeAccelerometer;
- (bool)initializeGravity;
- (bool)initializeMagnetometer;
- (bool)initializeOrientation;
- (void)cancel;
@end

View File

@@ -0,0 +1,265 @@
#import <RNReanimated/ReanimatedSensor.h>
#if !TARGET_OS_TV && !TARGET_OS_OSX && !TARGET_OS_VISION
@implementation ReanimatedSensor
- (instancetype)init:(ReanimatedSensorType)sensorType
interval:(int)interval
iosReferenceFrame:(int)iosReferenceFrame
setter:(void (^)(double[], int))setter
{
self = [super init];
_sensorType = sensorType;
if (interval == -1) {
_interval = 1.0 / UIScreen.mainScreen.maximumFramesPerSecond;
} else {
_interval = interval / 1000.0; // in seconds
}
_referenceFrame = iosReferenceFrame;
_setter = setter;
_motionManager = [[CMMotionManager alloc] init];
return self;
}
- (bool)initialize
{
if (_sensorType == ACCELEROMETER) {
return [self initializeAccelerometer];
} else if (_sensorType == GYROSCOPE) {
return [self initializeGyroscope];
} else if (_sensorType == GRAVITY) {
return [self initializeGravity];
} else if (_sensorType == MAGNETIC_FIELD) {
return [self initializeMagnetometer];
} else if (_sensorType == ROTATION_VECTOR) {
return [self initializeOrientation];
}
return false;
}
- (bool)initializeGyroscope
{
if (![_motionManager isGyroAvailable]) {
return false;
}
[_motionManager setGyroUpdateInterval:_interval];
[_motionManager startGyroUpdates];
[_motionManager
startGyroUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMGyroData *sensorData, NSError *error) {
double currentTime = CACurrentMediaTime() * 1000;
if (currentTime - self->_lastTimestamp < self->_interval) {
return;
}
double data[] = {sensorData.rotationRate.x, sensorData.rotationRate.y, sensorData.rotationRate.z};
self->_setter(data, [self getInterfaceOrientation]);
self->_lastTimestamp = currentTime;
}];
return true;
}
- (bool)initializeAccelerometer
{
if (![_motionManager isAccelerometerAvailable]) {
return false;
}
[_motionManager setAccelerometerUpdateInterval:_interval];
[_motionManager startAccelerometerUpdates];
[_motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMAccelerometerData *sensorData, NSError *error) {
double currentTime = CACurrentMediaTime() * 1000;
if (currentTime - self->_lastTimestamp < self->_interval) {
return;
}
double G = 9.81;
// convert G to m/s^2
double data[] = {
sensorData.acceleration.x * G,
sensorData.acceleration.y * G,
sensorData.acceleration.z * G};
self->_setter(data, [self getInterfaceOrientation]);
self->_lastTimestamp = currentTime;
}];
return true;
}
- (bool)initializeGravity
{
if (![_motionManager isDeviceMotionAvailable]) {
return false;
}
[_motionManager setDeviceMotionUpdateInterval:_interval];
[_motionManager setShowsDeviceMovementDisplay:YES];
[_motionManager
startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMDeviceMotion *sensorData, NSError *error) {
double currentTime = CACurrentMediaTime() * 1000;
if (currentTime - self->_lastTimestamp < self->_interval) {
return;
}
double G = 9.81;
// convert G to m/s^2
double data[] = {
sensorData.gravity.x * G, sensorData.gravity.y * G, sensorData.gravity.z * G};
self->_setter(data, [self getInterfaceOrientation]);
self->_lastTimestamp = currentTime;
}];
return true;
}
- (bool)initializeMagnetometer
{
if (![_motionManager isMagnetometerAvailable]) {
return false;
}
[_motionManager setMagnetometerUpdateInterval:_interval];
[_motionManager startMagnetometerUpdates];
[_motionManager
startMagnetometerUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMMagnetometerData *sensorData, NSError *error) {
double currentTime = CACurrentMediaTime() * 1000;
if (currentTime - self->_lastTimestamp < self->_interval) {
return;
}
double data[] = {
sensorData.magneticField.x, sensorData.magneticField.y, sensorData.magneticField.z};
self->_setter(data, [self getInterfaceOrientation]);
self->_lastTimestamp = currentTime;
}];
return true;
}
- (bool)initializeOrientation
{
if (![_motionManager isDeviceMotionAvailable]) {
return false;
}
[_motionManager setDeviceMotionUpdateInterval:_interval];
[_motionManager setShowsDeviceMovementDisplay:YES];
// _referenceFrame = Auto, on devices without magnetometer fall back to `XArbitraryZVertical`,
// `XArbitraryCorrectedZVertical` otherwise
if (_referenceFrame == 4) {
if (![_motionManager isMagnetometerAvailable]) {
_referenceFrame = 0;
} else {
_referenceFrame = 1;
}
}
// the binary shift works here because of the definition of CMAttitudeReferenceFrame
[_motionManager startDeviceMotionUpdatesUsingReferenceFrame:(1 << _referenceFrame)
toQueue:[NSOperationQueue mainQueue]
withHandler:^(CMDeviceMotion *sensorData, NSError *error) {
double currentTime = CACurrentMediaTime() * 1000;
if (currentTime - self->_lastTimestamp < self->_interval) {
return;
}
CMAttitude *attitude = sensorData.attitude;
double data[] = {
attitude.quaternion.x,
attitude.quaternion.y,
attitude.quaternion.z,
attitude.quaternion.w,
attitude.yaw,
attitude.pitch,
attitude.roll};
self->_setter(data, [self getInterfaceOrientation]);
self->_lastTimestamp = currentTime;
}];
return true;
}
- (void)cancel
{
if (_sensorType == ACCELEROMETER) {
[_motionManager stopAccelerometerUpdates];
} else if (_sensorType == GYROSCOPE) {
[_motionManager stopGyroUpdates];
} else if (_sensorType == GRAVITY) {
[_motionManager stopDeviceMotionUpdates];
} else if (_sensorType == MAGNETIC_FIELD) {
[_motionManager stopMagnetometerUpdates];
} else if (_sensorType == ROTATION_VECTOR) {
[_motionManager stopDeviceMotionUpdates];
}
}
- (int)getInterfaceOrientation
{
UIInterfaceOrientation orientation = UIInterfaceOrientationPortrait;
if (@available(iOS 13.0, *)) {
orientation = UIApplication.sharedApplication.windows.firstObject.windowScene.interfaceOrientation;
} else {
orientation = UIApplication.sharedApplication.statusBarOrientation;
}
switch (orientation) {
case UIInterfaceOrientationLandscapeLeft:
return 270;
case UIInterfaceOrientationLandscapeRight:
return 90;
case UIInterfaceOrientationPortraitUpsideDown:
return 180;
default:
return 0;
}
}
@end
#else
@implementation ReanimatedSensor
- (instancetype)init:(ReanimatedSensorType)sensorType
interval:(int)interval
iosReferenceFrame:(int)iosReferenceFrame
setter:(void (^)(double[], int))setter
{
self = [super init];
return self;
}
- (bool)initialize
{
return false;
}
- (bool)initializeGyroscope
{
return false;
}
- (bool)initializeAccelerometer
{
return false;
}
- (bool)initializeGravity
{
return false;
}
- (bool)initializeMagnetometer
{
return false;
}
- (bool)initializeOrientation
{
return false;
}
- (void)cancel
{
}
@end
#endif

View File

@@ -0,0 +1,15 @@
#import <RNReanimated/ReanimatedSensorType.h>
@interface ReanimatedSensorContainer : NSObject {
NSNumber *_nextSensorId;
NSMutableDictionary *_sensors;
}
- (instancetype)init;
- (int)registerSensor:(ReanimatedSensorType)sensorType
interval:(int)interval
iosReferenceFrame:(int)iosReferenceFrame
setter:(void (^)(double[], int))setter;
- (void)unregisterSensor:(int)sensorId;
@end

View File

@@ -0,0 +1,46 @@
#import <Foundation/Foundation.h>
#import <RNReanimated/ReanimatedSensor.h>
#import <RNReanimated/ReanimatedSensorContainer.h>
static NSNumber *_nextSensorId = nil;
@implementation ReanimatedSensorContainer
- (instancetype)init
{
self = [super init];
_sensors = [[NSMutableDictionary alloc] init];
_nextSensorId = @0;
return self;
}
- (int)registerSensor:(ReanimatedSensorType)sensorType
interval:(int)interval
iosReferenceFrame:(int)iosReferenceFrame
setter:(void (^)(double[], int))setter
{
ReanimatedSensor *sensor = [[ReanimatedSensor alloc] init:sensorType
interval:interval
iosReferenceFrame:iosReferenceFrame
setter:setter];
if ([sensor initialize]) {
NSNumber *sensorId = [_nextSensorId copy];
_nextSensorId = [NSNumber numberWithInt:[_nextSensorId intValue] + 1];
[_sensors setObject:sensor forKey:sensorId];
return [sensorId intValue];
} else {
return -1;
}
}
- (void)unregisterSensor:(int)sensorId
{
NSNumber *_sensorId = [NSNumber numberWithInt:sensorId];
if (_sensors[_sensorId] == nil) {
return;
}
[_sensors[_sensorId] cancel];
[_sensors removeObjectForKey:_sensorId];
}
@end

View File

@@ -0,0 +1,7 @@
typedef NS_ENUM(NSUInteger, ReanimatedSensorType) {
ACCELEROMETER = 1,
GYROSCOPE = 2,
GRAVITY = 3,
MAGNETIC_FIELD = 4,
ROTATION_VECTOR = 5,
};