[WIP] Migrated View Managers over to new architecture

This commit is contained in:
Nick Lockwood 2015-03-01 15:33:55 -08:00
parent 33290fb1e2
commit 3b11b9d6c3
47 changed files with 991 additions and 1093 deletions

View File

@ -7,46 +7,46 @@
objects = {
/* Begin PBXBuildFile section */
1316A21A1AA397CA00C0188E /* libRCTNetworkImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1316A2101AA3871A00C0188E /* libRCTNetworkImage.a */; };
1316A21B1AA397CA00C0188E /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1316A2081AA386C700C0188E /* libRCTText.a */; };
1316A21C1AA397CA00C0188E /* libReactKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1316A2171AA3875D00C0188E /* libReactKit.a */; };
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
587651361A9EB175008B8F17 /* libRCTNetworkImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 587651281A9EB168008B8F17 /* libRCTNetworkImage.a */; };
587651371A9EB175008B8F17 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 587651301A9EB168008B8F17 /* libRCTText.a */; };
587651381A9EB175008B8F17 /* libReactKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 587651351A9EB168008B8F17 /* libReactKit.a */; };
587651571A9F862F008B8F17 /* libRCTDataManager.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5876514F1A9F8619008B8F17 /* libRCTDataManager.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
587651271A9EB168008B8F17 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 5876511C1A9EB168008B8F17 /* RCTNetworkImage.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 58B5115D1A9E6B3D00147676;
remoteInfo = RCTNetworkImage;
};
587651291A9EB168008B8F17 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 5876511C1A9EB168008B8F17 /* RCTNetworkImage.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 58B511681A9E6B3D00147676;
remoteInfo = RCTNetworkImageTests;
};
5876512F1A9EB168008B8F17 /* PBXContainerItemProxy */ = {
1316A2071AA386C700C0188E /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 5876511F1A9EB168008B8F17 /* RCTText.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 58B5119B1A9E6C1200147676;
remoteInfo = RCTText;
};
587651311A9EB168008B8F17 /* PBXContainerItemProxy */ = {
1316A2091AA386C700C0188E /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 5876511F1A9EB168008B8F17 /* RCTText.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 58B511A61A9E6C1300147676;
remoteInfo = RCTTextTests;
};
587651341A9EB168008B8F17 /* PBXContainerItemProxy */ = {
1316A20F1AA3871A00C0188E /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 5876511C1A9EB168008B8F17 /* RCTNetworkImage.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 58B5115D1A9E6B3D00147676;
remoteInfo = RCTNetworkImage;
};
1316A2111AA3871A00C0188E /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 5876511C1A9EB168008B8F17 /* RCTNetworkImage.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 58B511681A9E6B3D00147676;
remoteInfo = RCTNetworkImageTests;
};
1316A2161AA3875D00C0188E /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 587651221A9EB168008B8F17 /* ReactKit.xcodeproj */;
proxyType = 2;
@ -77,9 +77,9 @@
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
5876511C1A9EB168008B8F17 /* RCTNetworkImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetworkImage.xcodeproj; path = "/Users/sahrens/src/fbobjc-hg/Libraries/FBReactKit/js/react-native-github/Libraries/RCTStandardLibrary/../Image/RCTNetworkImage.xcodeproj"; sourceTree = "<absolute>"; };
5876511F1A9EB168008B8F17 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "/Users/sahrens/src/fbobjc-hg/Libraries/FBReactKit/js/react-native-github/Libraries/RCTStandardLibrary/../Text/RCTText.xcodeproj"; sourceTree = "<absolute>"; };
587651221A9EB168008B8F17 /* ReactKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactKit.xcodeproj; path = "/Users/sahrens/src/fbobjc-hg/Libraries/FBReactKit/js/react-native-github/Libraries/RCTStandardLibrary/../../ReactKit/ReactKit.xcodeproj"; sourceTree = "<absolute>"; };
5876511C1A9EB168008B8F17 /* RCTNetworkImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetworkImage.xcodeproj; path = ../../Libraries/Image/RCTNetworkImage.xcodeproj; sourceTree = SOURCE_ROOT; };
5876511F1A9EB168008B8F17 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = ../../Libraries/Text/RCTText.xcodeproj; sourceTree = SOURCE_ROOT; };
587651221A9EB168008B8F17 /* ReactKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactKit.xcodeproj; path = ../../ReactKit/ReactKit.xcodeproj; sourceTree = SOURCE_ROOT; };
587651491A9F8619008B8F17 /* RCTDataManager.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTDataManager.xcodeproj; path = ../../Libraries/Network/RCTDataManager.xcodeproj; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -88,16 +88,53 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
1316A21A1AA397CA00C0188E /* libRCTNetworkImage.a in Frameworks */,
1316A21B1AA397CA00C0188E /* libRCTText.a in Frameworks */,
1316A21C1AA397CA00C0188E /* libReactKit.a in Frameworks */,
587651571A9F862F008B8F17 /* libRCTDataManager.a in Frameworks */,
587651361A9EB175008B8F17 /* libRCTNetworkImage.a in Frameworks */,
587651371A9EB175008B8F17 /* libRCTText.a in Frameworks */,
587651381A9EB175008B8F17 /* libReactKit.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
1316A2031AA386C700C0188E /* Products */ = {
isa = PBXGroup;
children = (
1316A2081AA386C700C0188E /* libRCTText.a */,
1316A20A1AA386C700C0188E /* RCTTextTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
1316A20B1AA3871A00C0188E /* Products */ = {
isa = PBXGroup;
children = (
1316A2101AA3871A00C0188E /* libRCTNetworkImage.a */,
1316A2121AA3871A00C0188E /* RCTNetworkImageTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
1316A2131AA3875D00C0188E /* Products */ = {
isa = PBXGroup;
children = (
1316A2171AA3875D00C0188E /* libReactKit.a */,
);
name = Products;
sourceTree = "<group>";
};
1316A21D1AA397F400C0188E /* Libraries */ = {
isa = PBXGroup;
children = (
587651221A9EB168008B8F17 /* ReactKit.xcodeproj */,
587651491A9F8619008B8F17 /* RCTDataManager.xcodeproj */,
5876511C1A9EB168008B8F17 /* RCTNetworkImage.xcodeproj */,
5876511F1A9EB168008B8F17 /* RCTText.xcodeproj */,
);
name = Libraries;
sourceTree = "<group>";
};
13B07FAE1A68108700A75B9A /* UIExplorer */ = {
isa = PBXGroup;
children = (
@ -111,32 +148,6 @@
name = UIExplorer;
sourceTree = "<group>";
};
5876511D1A9EB168008B8F17 /* Products */ = {
isa = PBXGroup;
children = (
587651281A9EB168008B8F17 /* libRCTNetworkImage.a */,
5876512A1A9EB168008B8F17 /* RCTNetworkImageTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
587651201A9EB168008B8F17 /* Products */ = {
isa = PBXGroup;
children = (
587651301A9EB168008B8F17 /* libRCTText.a */,
587651321A9EB168008B8F17 /* RCTTextTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
587651231A9EB168008B8F17 /* Products */ = {
isa = PBXGroup;
children = (
587651351A9EB168008B8F17 /* libReactKit.a */,
);
name = Products;
sourceTree = "<group>";
};
5876514A1A9F8619008B8F17 /* Products */ = {
isa = PBXGroup;
children = (
@ -150,10 +161,7 @@
isa = PBXGroup;
children = (
13B07FAE1A68108700A75B9A /* UIExplorer */,
587651491A9F8619008B8F17 /* RCTDataManager.xcodeproj */,
5876511C1A9EB168008B8F17 /* RCTNetworkImage.xcodeproj */,
5876511F1A9EB168008B8F17 /* RCTText.xcodeproj */,
587651221A9EB168008B8F17 /* ReactKit.xcodeproj */,
1316A21D1AA397F400C0188E /* Libraries */,
83CBBA001A601CBA00E9B192 /* Products */,
);
sourceTree = "<group>";
@ -212,15 +220,15 @@
ProjectRef = 587651491A9F8619008B8F17 /* RCTDataManager.xcodeproj */;
},
{
ProductGroup = 5876511D1A9EB168008B8F17 /* Products */;
ProductGroup = 1316A20B1AA3871A00C0188E /* Products */;
ProjectRef = 5876511C1A9EB168008B8F17 /* RCTNetworkImage.xcodeproj */;
},
{
ProductGroup = 587651201A9EB168008B8F17 /* Products */;
ProductGroup = 1316A2031AA386C700C0188E /* Products */;
ProjectRef = 5876511F1A9EB168008B8F17 /* RCTText.xcodeproj */;
},
{
ProductGroup = 587651231A9EB168008B8F17 /* Products */;
ProductGroup = 1316A2131AA3875D00C0188E /* Products */;
ProjectRef = 587651221A9EB168008B8F17 /* ReactKit.xcodeproj */;
},
);
@ -232,39 +240,39 @@
/* End PBXProject section */
/* Begin PBXReferenceProxy section */
587651281A9EB168008B8F17 /* libRCTNetworkImage.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTNetworkImage.a;
remoteRef = 587651271A9EB168008B8F17 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
5876512A1A9EB168008B8F17 /* RCTNetworkImageTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = RCTNetworkImageTests.xctest;
remoteRef = 587651291A9EB168008B8F17 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
587651301A9EB168008B8F17 /* libRCTText.a */ = {
1316A2081AA386C700C0188E /* libRCTText.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTText.a;
remoteRef = 5876512F1A9EB168008B8F17 /* PBXContainerItemProxy */;
remoteRef = 1316A2071AA386C700C0188E /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
587651321A9EB168008B8F17 /* RCTTextTests.xctest */ = {
1316A20A1AA386C700C0188E /* RCTTextTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = RCTTextTests.xctest;
remoteRef = 587651311A9EB168008B8F17 /* PBXContainerItemProxy */;
remoteRef = 1316A2091AA386C700C0188E /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
587651351A9EB168008B8F17 /* libReactKit.a */ = {
1316A2101AA3871A00C0188E /* libRCTNetworkImage.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libRCTNetworkImage.a;
remoteRef = 1316A20F1AA3871A00C0188E /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
1316A2121AA3871A00C0188E /* RCTNetworkImageTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = RCTNetworkImageTests.xctest;
remoteRef = 1316A2111AA3871A00C0188E /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
1316A2171AA3875D00C0188E /* libReactKit.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libReactKit.a;
remoteRef = 587651341A9EB168008B8F17 /* PBXContainerItemProxy */;
remoteRef = 1316A2161AA3875D00C0188E /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
5876514F1A9F8619008B8F17 /* libRCTDataManager.a */ = {

View File

@ -8,7 +8,7 @@
var EventEmitter = require('EventEmitter');
var React = require('React');
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
var { RKUIManager } = require('NativeModules');
var { RKNavigatorManager } = require('NativeModules');
var StyleSheet = require('StyleSheet');
var StaticContainer = require('StaticContainer.react');
var View = require('View');
@ -127,7 +127,7 @@ var RKNavigatorItem = createReactIOSNativeComponentClass({
var NavigatorTransitionerIOS = React.createClass({
requestSchedulingNavigation: function(cb) {
RKUIManager.requestSchedulingJavaScriptNavigation(
RKNavigatorManager.requestSchedulingJavaScriptNavigation(
this.getNodeHandle(),
logError,
cb

View File

@ -6,7 +6,7 @@
'use strict';
var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
var RCTLocationObserver = require('NativeModules').RCTLocationObserver;
var RCTLocationObserver = require('NativeModules').RKLocationObserver;
var invariant = require('invariant');
var logError = require('logError');

View File

@ -6,7 +6,8 @@
@interface RCTNetworkImageView : UIView
- (instancetype)initWithFrame:(CGRect)frame imageDownloader:(RCTImageDownloader *)imageDownloader;
- (instancetype)initWithFrame:(CGRect)frame
imageDownloader:(RCTImageDownloader *)imageDownloader NS_DESIGNATED_INITIALIZER;
/**
* An image that will appear while the view is loading the image from the network,

View File

@ -27,11 +27,6 @@
return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
RCT_NOT_DESIGNATED_INITIALIZER();
}
- (NSURL *)imageURL
{
// We clear our backing layer's imageURL when we are not in a window for a while,

View File

@ -65,8 +65,7 @@
} else {
RCTLogMustFix(@"unsupported query type %@", queryType);
return;
RCTLogError(@"unsupported query type %@", queryType);
}
}

View File

@ -269,6 +269,10 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../ReactKit/**",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"/Users/nicklockwood/fbobjc-hg/Libraries/FBReactKit/js/react-native-github/ReactKit/build/Debug-iphoneos",
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -283,6 +287,10 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../ReactKit/**",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"/Users/nicklockwood/fbobjc-hg/Libraries/FBReactKit/js/react-native-github/ReactKit/build/Debug-iphoneos",
);
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;

View File

@ -24,17 +24,7 @@
}
RCT_REMAP_VIEW_PROPERTY(containerBackgroundColor, backgroundColor)
- (void)set_textAlign:(id)json
forShadowView:(RCTShadowText *)shadowView
withDefaultView:(RCTShadowText *)defaultView
{
shadowView.textAlign = json ? [RCTConvert NSTextAlignment:json] : defaultView.textAlign;
}
- (void)set_numberOfLines:(id)json
forView:(RCTText *)view
withDefaultView:(RCTText *)defaultView
RCT_CUSTOM_VIEW_PROPERTY(numberOfLines, RCTText *)
{
NSLineBreakMode truncationMode = NSLineBreakByClipping;
view.numberOfLines = json ? [RCTConvert NSInteger:json] : defaultView.numberOfLines;
@ -44,31 +34,33 @@ RCT_REMAP_VIEW_PROPERTY(containerBackgroundColor, backgroundColor)
view.lineBreakMode = truncationMode;
}
- (void)set_numberOfLines:(id)json
forShadowView:(RCTShadowText *)shadowView
withDefaultView:(RCTShadowText *)defaultView
RCT_CUSTOM_SHADOW_PROPERTY(backgroundColor, RCTShadowText *)
{
view.textBackgroundColor = json ? [RCTConvert UIColor:json] : defaultView.textBackgroundColor;
}
RCT_CUSTOM_SHADOW_PROPERTY(containerBackgroundColor, RCTShadowText *)
{
view.backgroundColor = json ? [RCTConvert UIColor:json] : defaultView.backgroundColor;
view.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet;
}
RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, RCTShadowText *)
{
NSLineBreakMode truncationMode = NSLineBreakByClipping;
shadowView.maxNumberOfLines = json ? [RCTConvert NSInteger:json] : defaultView.maxNumberOfLines;
if (shadowView.maxNumberOfLines > 0) {
view.maxNumberOfLines = json ? [RCTConvert NSInteger:json] : defaultView.maxNumberOfLines;
if (view.maxNumberOfLines > 0) {
truncationMode = NSLineBreakByTruncatingTail;
}
shadowView.truncationMode = truncationMode;
view.truncationMode = truncationMode;
}
RCT_CUSTOM_SHADOW_PROPERTY(textAlign, RCTShadowText *)
{
view.textAlign = json ? [RCTConvert NSTextAlignment:json] : defaultView.textAlign;
}
- (void)set_backgroundColor:(id)json
forShadowView:(RCTShadowText *)shadowView
withDefaultView:(RCTShadowText *)defaultView
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowText *)shadowView
{
shadowView.textBackgroundColor = json ? [RCTConvert UIColor:json] : defaultView.textBackgroundColor;
}
- (void)set_containerBackgroundColor:(id)json
forShadowView:(RCTShadowText *)shadowView
withDefaultView:(RCTShadowText *)defaultView
{
shadowView.backgroundColor = json ? [RCTConvert UIColor:json] : defaultView.backgroundColor;
shadowView.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet;
//TODO: This could be a cleaner replacement for uiBlockToAmendWithShadowViewRegistry
return nil;
}
// TODO: the purpose of this block is effectively just to copy properties from the shadow views

View File

@ -6,32 +6,16 @@
@class RCTBridge;
@class RCTEventDispatcher;
@class RCTRootView;
/**
* Utilities for constructing common response objects. When sending a
* systemError back to JS, it's important to describe whether or not it was a
* system error, or API usage error. System errors should never happen and are
* therefore logged using `RCTLogError()`. API usage errors are expected if the
* API is misused and will therefore not be logged using `RCTLogError()`. The JS
* application code is expected to handle them. Regardless of type, each error
* should be logged at most once.
*/
static inline NSDictionary *RCTSystemErrorObject(NSString *msg)
{
return @{@"systemError": msg ?: @""};
}
static inline NSDictionary *RCTAPIErrorObject(NSString *msg)
{
return @{@"apiError": msg ?: @""};
}
/**
* This block can be used to instantiate modules that require additional
* init parameters, or additional configuration prior to being used.
* The bridge will call this block to instatiate the modules, and will
* be responsible for invalidating/releasing them when the bridge is destroyed.
* For this reason, the block should always return new module instances, and
* module instances should not be shared between bridges.
*/
typedef NSArray *(^RCTBridgeModuleProviderBlock)(RCTBridge *bridge);
typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);
/**
* Async batched bridge used to communicate with the JavaScript application.
@ -42,12 +26,12 @@ typedef NSArray *(^RCTBridgeModuleProviderBlock)(RCTBridge *bridge);
* The designated initializer. This creates a new bridge on top of the specified
* executor. The bridge should then be used for all subsequent communication
* with the JavaScript code running in the executor. Modules will be automatically
* instantiated using the default contructor, but you can optionally pass in a
* module provider block to manually instantiate modules that require additional
* init parameters or configuration.
* instantiated using the default contructor, but you can optionally pass in an
* array of pre-initialized module instances if they require additional init
* parameters or configuration.
*/
- (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
moduleProvider:(RCTBridgeModuleProviderBlock)block NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithExecutor:(id<RCTJavaScriptExecutor>)executor
moduleProvider:(RCTBridgeModuleProviderBlock)block NS_DESIGNATED_INITIALIZER;
/**
* This method is used to call functions in the JavaScript application context.
@ -81,16 +65,6 @@ typedef NSArray *(^RCTBridgeModuleProviderBlock)(RCTBridge *bridge);
*/
@property (nonatomic, readonly) dispatch_queue_t shadowQueue;
// For use in implementing delegates, which may need to queue responses.
- (RCTResponseSenderBlock)createResponseSenderBlock:(NSInteger)callbackID;
/**
* Register a root view with the bridge. Theorectically, a single bridge can
* support multiple root views, however this feature is not currently exposed
* and may eventually be removed.
*/
- (void)registerRootView:(RCTRootView *)rootView;
/**
* Global logging function that will print to both xcode and JS debugger consoles.
*

View File

@ -3,11 +3,12 @@
#import "RCTBridge.h"
#import <dlfcn.h>
#import <mach-o/getsect.h>
#import <mach-o/dyld.h>
#import <objc/message.h>
#import <objc/runtime.h>
#import <mach-o/dyld.h>
#import <mach-o/getsect.h>
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
@ -26,60 +27,6 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
RCTBridgeFieldFlushDateMillis
};
/**
* This private class is used as a container for exported method info
*/
@interface RCTModuleMethod : NSObject
@property (readonly, nonatomic, assign) SEL selector;
@property (readonly, nonatomic, copy) NSString *JSMethodName;
@property (readonly, nonatomic, assign) NSUInteger arity;
@property (readonly, nonatomic, copy) NSIndexSet *blockArgumentIndexes;
@end
@implementation RCTModuleMethod
- (instancetype)initWithSelector:(SEL)selector
JSMethodName:(NSString *)JSMethodName
arity:(NSUInteger)arity
blockArgumentIndexes:(NSIndexSet *)blockArgumentIndexes
{
if ((self = [super init])) {
_selector = selector;
_JSMethodName = [JSMethodName copy];
_arity = arity;
_blockArgumentIndexes = [blockArgumentIndexes copy];
}
return self;
}
- (NSString *)description
{
NSString *blocks = @"no block args";
if (self.blockArgumentIndexes.count > 0) {
NSMutableString *indexString = [NSMutableString string];
[self.blockArgumentIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
[indexString appendFormat:@", %tu", idx];
}];
blocks = [NSString stringWithFormat:@"block args at %@", [indexString substringFromIndex:2]];
}
return [NSString stringWithFormat:@"<%@: %p; exports -%@ as %@; %@>", NSStringFromClass(self.class), self, NSStringFromSelector(self.selector), self.JSMethodName, blocks];
}
@end
#ifdef __LP64__
typedef uint64_t RCTExportValue;
typedef struct section_64 RCTExportSection;
#define RCTGetSectByNameFromHeader getsectbynamefromheader_64
#else
typedef uint32_t RCTExportValue;
typedef struct section RCTExportSection;
#define RCTGetSectByNameFromHeader getsectbynamefromheader
#endif
/**
* This function returns the module name for a given class.
*/
@ -88,18 +35,6 @@ static NSString *RCTModuleNameForClass(Class cls)
return [cls respondsToSelector:@selector(moduleName)] ? [cls moduleName] : NSStringFromClass(cls);
}
/**
* This function instantiates a new module instance.
*/
static id<RCTBridgeModule> RCTCreateModuleInstance(Class cls, RCTBridge *bridge)
{
if ([cls instancesRespondToSelector:@selector(initWithBridge:)]) {
return [[cls alloc] initWithBridge:bridge];
} else {
return [[cls alloc] init];
}
}
/**
* This function scans all classes available at runtime and returns an array
* of all JSMethods registered.
@ -111,22 +46,11 @@ static NSArray *RCTJSMethods(void)
dispatch_once(&onceToken, ^{
NSMutableSet *uniqueMethods = [NSMutableSet set];
unsigned int classCount;
Class *classes = objc_copyClassList(&classCount);
for (unsigned int i = 0; i < classCount; i++) {
Class cls = classes[i];
if (!class_getSuperclass(cls)) {
// Class has no superclass - it's probably something weird
continue;
}
RCTEnumerateClasses(^(__unsafe_unretained Class cls) {
if (RCTClassOverridesClassMethod(cls, @selector(JSMethods))) {
[uniqueMethods addObjectsFromArray:[cls JSMethods]];
}
}
free(classes);
});
JSMethods = [uniqueMethods allObjects];
});
@ -147,38 +71,216 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
modules = [NSMutableArray array];
RCTModuleNamesByID = [NSMutableArray array];
unsigned int classCount;
Class *classes = objc_copyClassList(&classCount);
for (unsigned int i = 0; i < classCount; i++) {
RCTEnumerateClasses(^(__unsafe_unretained Class cls) {
if ([cls conformsToProtocol:@protocol(RCTBridgeModule)]) {
Class cls = classes[i];
// Add module
[(NSMutableArray *)modules addObject:cls];
if (!class_getSuperclass(cls)) {
// Class has no superclass - it's probably something weird
continue;
// Add module name
NSString *moduleName = RCTModuleNameForClass(cls);
[(NSMutableArray *)RCTModuleNamesByID addObject:moduleName];
}
if (![cls conformsToProtocol:@protocol(RCTBridgeModule)]) {
// Not an RCTBridgeModule
continue;
}
// Add module
[(NSMutableArray *)modules addObject:cls];
// Add module name
NSString *moduleName = RCTModuleNameForClass(cls);
[(NSMutableArray *)RCTModuleNamesByID addObject:moduleName];
}
free(classes);
});
modules = [modules copy];
RCTModuleNamesByID = [RCTModuleNamesByID copy];
});
return modules;
}
@interface RCTBridge ()
- (void)_invokeAndProcessModule:(NSString *)module
method:(NSString *)method
arguments:(NSArray *)args;
@end
/**
* This private class is used as a container for exported method info
*/
@interface RCTModuleMethod : NSObject
@property (nonatomic, copy, readonly) NSString *moduleClassName;
@property (nonatomic, copy, readonly) NSString *JSMethodName;
@end
@implementation RCTModuleMethod
{
BOOL _isClassMethod;
Class _moduleClass;
SEL _selector;
NSMethodSignature *_methodSignature;
NSArray *_argumentBlocks;
NSString *_methodName;
}
- (instancetype)initWithMethodName:(NSString *)methodName
JSMethodName:(NSString *)JSMethodName
{
if ((self = [super init])) {
_methodName = methodName;
NSArray *parts = [[methodName substringWithRange:NSMakeRange(2, methodName.length - 3)] componentsSeparatedByString:@" "];
// Parse class and method
_moduleClassName = parts[0];
NSRange categoryRange = [_moduleClassName rangeOfString:@"("];
if (categoryRange.length)
{
_moduleClassName = [_moduleClassName substringToIndex:categoryRange.location];
}
// Extract class and method details
_isClassMethod = [methodName characterAtIndex:0] == '+';
_moduleClass = NSClassFromString(_moduleClassName);
_selector = NSSelectorFromString(parts[1]);
_JSMethodName = JSMethodName ?: [NSStringFromSelector(_selector) componentsSeparatedByString:@":"][0];
#if DEBUG
// Sanity check
RCTAssert([_moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
@"You are attempting to export the method %@, but %@ does not \
conform to the RCTBridgeModule Protocol", methodName, _moduleClassName);
#endif
// Get method signature
_methodSignature = _isClassMethod ?
[_moduleClass methodSignatureForSelector:_selector] :
[_moduleClass instanceMethodSignatureForSelector:_selector];
// Process arguments
NSUInteger numberOfArguments = _methodSignature.numberOfArguments;
NSMutableArray *argumentBlocks = [[NSMutableArray alloc] initWithCapacity:numberOfArguments - 2];
for (NSUInteger i = 2; i < numberOfArguments; i++) {
const char *argumentType = [_methodSignature getArgumentTypeAtIndex:i];
switch (argumentType[0]) {
#define RCT_ARG_BLOCK(_logic) \
[argumentBlocks addObject:^(RCTBridge *bridge, NSInvocation *invocation, NSUInteger index, id json) { \
_logic \
[invocation setArgument:&value atIndex:index]; \
}]; \
#define RCT_CASE(_value, _class, _logic) \
case _value: { \
RCT_ARG_BLOCK( \
if (json && ![json isKindOfClass:[_class class]]) { \
RCTLogError(@"Argument %tu (%@) of %@.%@ should be of type %@", index, \
json, RCTModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \
return; \
} \
_logic \
) \
break; \
}
RCT_CASE(':', NSString, SEL value = NSSelectorFromString(json); );
RCT_CASE('*', NSString, const char *value = [json UTF8String]; );
#define RCT_SIMPLE_CASE(_value, _type, _selector) \
case _value: { \
RCT_ARG_BLOCK( \
if (json && ![json respondsToSelector:@selector(_selector)]) { \
RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \
index, json, RCTModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \
return; \
} \
_type value = [json _selector]; \
) \
break; \
}
RCT_SIMPLE_CASE('c', char, charValue)
RCT_SIMPLE_CASE('C', unsigned char, unsignedCharValue)
RCT_SIMPLE_CASE('s', short, shortValue)
RCT_SIMPLE_CASE('S', unsigned short, unsignedShortValue)
RCT_SIMPLE_CASE('i', int, intValue)
RCT_SIMPLE_CASE('I', unsigned int, unsignedIntValue)
RCT_SIMPLE_CASE('l', long, longValue)
RCT_SIMPLE_CASE('L', unsigned long, unsignedLongValue)
RCT_SIMPLE_CASE('q', long long, longLongValue)
RCT_SIMPLE_CASE('Q', unsigned long long, unsignedLongLongValue)
RCT_SIMPLE_CASE('f', float, floatValue)
RCT_SIMPLE_CASE('d', double, doubleValue)
RCT_SIMPLE_CASE('B', BOOL, boolValue)
default: {
static const char *blockType = @encode(typeof(^{}));
if (!strcmp(argumentType, blockType)) {
RCT_ARG_BLOCK(
if (json && ![json isKindOfClass:[NSNumber class]]) {
RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index,
json, RCTModuleNameForClass(_moduleClass), _JSMethodName);
return;
}
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
__autoreleasing id value = (json ? ^(NSArray *args) {
[bridge _invokeAndProcessModule:@"BatchedBridge"
method:@"invokeCallbackAndReturnFlushedQueue"
arguments:@[json, args]];
} : ^(NSArray *unused) {});
)
} else {
RCT_ARG_BLOCK( id value = json; )
}
break;
}
}
}
_argumentBlocks = [argumentBlocks copy];
}
return self;
}
- (void)invokeWithBridge:(RCTBridge *)bridge
module:(id)module
arguments:(NSArray *)arguments
{
#if DEBUG
// Sanity check
RCTAssert([module class] == _moduleClass, @"Attempted to invoke method \
%@ on a module of class %@", _methodName, [module class]);
#endif
// Safety check
if (arguments.count != _argumentBlocks.count) {
RCTLogError(@"%@.%@ was called with %zd arguments, but expects %zd",
RCTModuleNameForClass(_moduleClass), _JSMethodName,
arguments.count, _argumentBlocks.count);
return;
}
// Create invocation (we can't re-use this as it wouldn't be thread-safe)
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:_methodSignature];
[invocation setArgument:&_selector atIndex:1];
// Set arguments
NSUInteger index = 0;
for (id json in arguments) {
id arg = (json == [NSNull null]) ? nil : json;
void (^block)(RCTBridge *, NSInvocation *, NSUInteger, id) = _argumentBlocks[index];
block(bridge, invocation, index + 2, arg);
index ++;
}
// Invoke method
[invocation invokeWithTarget:_isClassMethod ? [module class] : module];
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@: %p; exports %@ as %@;>", NSStringFromClass(self.class), self, _methodName, _JSMethodName];
}
@end
/**
* This function parses the exported methods inside RCTBridgeModules and
* generates an array of arrays of RCTModuleMethod objects, keyed
@ -193,6 +295,16 @@ static RCTSparseArray *RCTExportedMethodsByModuleID(void)
Dl_info info;
dladdr(&RCTExportedMethodsByModuleID, &info);
#ifdef __LP64__
typedef uint64_t RCTExportValue;
typedef struct section_64 RCTExportSection;
#define RCTGetSectByNameFromHeader getsectbynamefromheader_64
#else
typedef uint32_t RCTExportValue;
typedef struct section RCTExportSection;
#define RCTGetSectByNameFromHeader getsectbynamefromheader
#endif
const RCTExportValue mach_header = (RCTExportValue)info.dli_fbase;
const RCTExportSection *section = RCTGetSectByNameFromHeader((void *)mach_header, "__DATA", "RCTExport");
@ -202,53 +314,23 @@ static RCTSparseArray *RCTExportedMethodsByModuleID(void)
NSArray *classes = RCTBridgeModuleClassesByModuleID();
NSMutableDictionary *methodsByModuleClassName = [NSMutableDictionary dictionaryWithCapacity:[classes count]];
NSCharacterSet *plusMinusCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"+-"];
for (RCTExportValue addr = section->offset;
addr < section->offset + section->size;
addr += sizeof(id) * 2) {
addr += sizeof(const char **) * 2) {
const char **entry = (const char **)(mach_header + addr);
NSScanner *scanner = [NSScanner scannerWithString:@(entry[0])];
// Get data entry
const char **entries = (const char **)(mach_header + addr);
NSString *plusMinus;
if (![scanner scanCharactersFromSet:plusMinusCharacterSet intoString:&plusMinus]) continue;
if (![scanner scanString:@"[" intoString:NULL]) continue;
NSString *className;
if (![scanner scanUpToString:@" " intoString:&className]) continue;
[scanner scanString:@" " intoString:NULL];
NSString *selectorName;
if (![scanner scanUpToString:@"]" intoString:&selectorName]) continue;
Class moduleClass = NSClassFromString(className);
if (moduleClass == Nil) continue;
SEL selector = NSSelectorFromString(selectorName);
Method method = ([plusMinus characterAtIndex:0] == '+' ? class_getClassMethod : class_getInstanceMethod)(moduleClass, selector);
if (method == nil) continue;
unsigned int argumentCount = method_getNumberOfArguments(method);
NSMutableIndexSet *blockArgumentIndexes = [NSMutableIndexSet indexSet];
static const char *blockType = @encode(typeof(^{}));
for (unsigned int i = 2; i < argumentCount; i++) {
char *type = method_copyArgumentType(method, i);
if (!strcmp(type, blockType)) {
[blockArgumentIndexes addIndex:i - 2];
}
free(type);
}
NSString *JSMethodName = strlen(entry[1]) ? @(entry[1]) : [NSStringFromSelector(selector) componentsSeparatedByString:@":"][0];
// Create method
RCTModuleMethod *moduleMethod =
[[RCTModuleMethod alloc] initWithSelector:selector
JSMethodName:JSMethodName
arity:method_getNumberOfArguments(method) - 2
blockArgumentIndexes:blockArgumentIndexes];
[[RCTModuleMethod alloc] initWithMethodName:@(entries[0])
JSMethodName:strlen(entries[1]) ? @(entries[1]) : nil];
NSArray *methods = methodsByModuleClassName[className];
methodsByModuleClassName[className] = methods ? [methods arrayByAddingObject:moduleMethod] : @[moduleMethod];
// Cache method
NSArray *methods = methodsByModuleClassName[moduleMethod.moduleClassName];
methodsByModuleClassName[moduleMethod.moduleClassName] =
methods ? [methods arrayByAddingObject:moduleMethod] : @[moduleMethod];
}
methodsByModuleID = [[RCTSparseArray alloc] initWithCapacity:[classes count]];
@ -256,7 +338,7 @@ static RCTSparseArray *RCTExportedMethodsByModuleID(void)
methodsByModuleID[moduleID] = methodsByModuleClassName[NSStringFromClass(moduleClass)];
}];
});
return methodsByModuleID;
}
@ -303,19 +385,12 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName)
@"type": @"remote",
};
}];
NSDictionary *module = @{
@"moduleID": @(moduleID),
@"methods": methodsByName
};
// Add static constants
if (RCTClassOverridesClassMethod(moduleClass, @selector(constantsToExport))) {
NSMutableDictionary *mutableModule = [module mutableCopy];
mutableModule[@"constants"] = [moduleClass constantsToExport] ?: @{};
module = [mutableModule copy];
}
remoteModuleConfigByClassName[NSStringFromClass(moduleClass)] = module;
}];
});
@ -324,14 +399,15 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName)
NSMutableDictionary *moduleConfig = [[NSMutableDictionary alloc] init];
[modulesByName enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, id<RCTBridgeModule> module, BOOL *stop) {
// Add "psuedo-constants"
// Add constants
NSMutableDictionary *config = remoteModuleConfigByClassName[NSStringFromClass([module class])];
if (RCTClassOverridesInstanceMethod([module class], @selector(constantsToExport))) {
NSMutableDictionary *mutableConfig = [NSMutableDictionary dictionaryWithDictionary:config];
NSMutableDictionary *mutableConstants = [NSMutableDictionary dictionaryWithDictionary:config[@"constants"]];
[mutableConstants addEntriesFromDictionary:[module constantsToExport]];
mutableConfig[@"constants"] = mutableConstants; // There's no real need to copy this
config = mutableConfig; // Nor this - receiver is unlikely to mutate it
if ([module respondsToSelector:@selector(constantsToExport)]) {
NSDictionary *constants = [module constantsToExport];
if (constants) {
NSMutableDictionary *mutableConfig = [NSMutableDictionary dictionaryWithDictionary:config];
mutableConfig[@"constants"] = constants; // There's no real need to copy this
config = mutableConfig; // Nor this - receiver is unlikely to mutate it
}
}
moduleConfig[moduleName] = config;
@ -340,6 +416,7 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName)
return moduleConfig;
}
/**
* As above, but for local modules/methods, which represent JS classes
* and methods that will be called by the native code via the bridge.
@ -417,21 +494,19 @@ static NSDictionary *RCTLocalModulesConfig()
static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (instancetype)initWithJavaScriptExecutor:(id<RCTJavaScriptExecutor>)javaScriptExecutor
moduleProvider:(RCTBridgeModuleProviderBlock)block
- (instancetype)initWithExecutor:(id<RCTJavaScriptExecutor>)executor
moduleProvider:(RCTBridgeModuleProviderBlock)block
{
if ((self = [super init])) {
_javaScriptExecutor = javaScriptExecutor;
_javaScriptExecutor = executor;
_latestJSExecutor = _javaScriptExecutor;
_eventDispatcher = [[RCTEventDispatcher alloc] initWithBridge:self];
_shadowQueue = dispatch_queue_create("com.facebook.ReactKit.ShadowQueue", DISPATCH_QUEUE_SERIAL);
// Register passed-in module instances
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
if (block) {
for (id<RCTBridgeModule> module in block(self)) {
preregisteredModules[RCTModuleNameForClass([module class])] = module;
}
for (id<RCTBridgeModule> module in block ? block() : nil) {
preregisteredModules[RCTModuleNameForClass([module class])] = module;
}
// Instantiate modules
@ -444,14 +519,14 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
// Preregistered instances takes precedence, no questions asked
if (!preregisteredModules[moduleName]) {
// It's OK to have a name collision as long as the second instance is nil
RCTAssert(RCTCreateModuleInstance(moduleClass, self) == nil,
RCTAssert([[moduleClass alloc] init] == nil,
@"Attempted to register RCTBridgeModule class %@ for the name '%@', \
but name was already registered by class %@", moduleClass,
moduleName, [modulesByName[moduleName] class]);
}
} else {
// Module name hasn't been used before, so go ahead and instantiate
id<RCTBridgeModule> module = RCTCreateModuleInstance(moduleClass, self);
id<RCTBridgeModule> module = [[moduleClass alloc] init];
if (module) {
_modulesByID[moduleID] = modulesByName[moduleName] = module;
}
@ -461,6 +536,13 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
// Store modules
_modulesByName = [modulesByName copy];
// Set bridge
for (id<RCTBridgeModule> module in _modulesByName.allValues) {
if ([module respondsToSelector:@selector(setBridge:)]) {
module.bridge = self;
}
}
// Inject module data into JS context
NSString *configJSON = RCTJSONStringify(@{
@"remoteModuleConfig": RCTRemoteModulesConfig(_modulesByName),
@ -470,12 +552,12 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
[_javaScriptExecutor injectJSONText:configJSON asGlobalObjectNamed:@"__fbBatchedBridgeConfig" callback:^(id err) {
dispatch_semaphore_signal(semaphore);
}];
if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC)) != 0) {
RCTLogMustFix(@"JavaScriptExecutor took too long to inject JSON object");
}
}
return self;
}
@ -501,23 +583,29 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)invalidate
{
// Release executor
if (_latestJSExecutor == _javaScriptExecutor) {
_latestJSExecutor = nil;
}
[_javaScriptExecutor invalidate];
_javaScriptExecutor = nil;
dispatch_sync(_shadowQueue, ^{
// Wait for queued methods to finish
dispatch_sync(self.shadowQueue, ^{
// Make sure all dispatchers have been executed before continuing
// TODO: is this still needed?
});
// Invalidate modules
for (id target in _modulesByID.allObjects) {
if ([target respondsToSelector:@selector(invalidate)]) {
[(id<RCTInvalidating>)target invalidate];
}
}
[_modulesByID removeAllObjects];
// Release modules (breaks retain cycle if module has strong bridge reference)
_modulesByID = nil;
_modulesByName = nil;
}
/**
@ -537,10 +625,10 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
NSNumber *moduleID = RCTLocalModuleIDs[moduleDotMethod];
RCTAssert(moduleID != nil, @"Module '%@' not registered.",
[[moduleDotMethod componentsSeparatedByString:@"."] firstObject]);
NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod];
RCTAssert(methodID != nil, @"Method '%@' not registered.", moduleDotMethod);
[self _invokeAndProcessModule:@"BatchedBridge"
method:@"callFunctionReturnFlushedQueue"
arguments:@[moduleID, methodID, args ?: @[]]];
@ -554,12 +642,12 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
onComplete(scriptLoadError);
return;
}
[_javaScriptExecutor executeJSCall:@"BatchedBridge"
method:@"flushedQueue"
arguments:@[]
callback:^(id objcValue, NSError *error) {
[self _handleBuffer:objcValue];
callback:^(id json, NSError *error) {
[self _handleBuffer:json];
onComplete(error);
}];
}];
@ -570,35 +658,28 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args
{
NSTimeInterval startJS = RCTTGetAbsoluteTime();
RCTJavaScriptCallback processResponse = ^(id objcValue, NSError *error) {
RCTJavaScriptCallback processResponse = ^(id json, NSError *error) {
NSTimeInterval startNative = RCTTGetAbsoluteTime();
[self _handleBuffer:objcValue];
[self _handleBuffer:json];
NSTimeInterval end = RCTTGetAbsoluteTime();
NSTimeInterval timeJS = startNative - startJS;
NSTimeInterval timeNative = end - startNative;
// TODO: surface this performance information somewhere
[[NSNotificationCenter defaultCenter] postNotificationName:@"PERF" object:nil userInfo:@{@"JS": @(timeJS * 1000000), @"Native": @(timeNative * 1000000)}];
[[NSNotificationCenter defaultCenter] postNotificationName:@"PERF" object:nil userInfo:@{
@"JS": @(timeJS * 1000000),
@"Native": @(timeNative * 1000000)
}];
};
[_javaScriptExecutor executeJSCall:module
method:method
arguments:args
callback:processResponse];
}
/**
* TODO (#5906496): Have responses piggy backed on a round trip with ObjC->JS requests.
*/
- (void)_sendResponseToJavaScriptCallbackID:(NSInteger)cbID args:(NSArray *)args
{
[self _invokeAndProcessModule:@"BatchedBridge"
method:@"invokeCallbackAndReturnFlushedQueue"
arguments:@[@(cbID), args]];
}
#pragma mark - Payload Processing
- (void)_handleBuffer:(id)buffer
@ -606,12 +687,12 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
if (buffer == nil || buffer == (id)kCFNull) {
return;
}
if (![buffer isKindOfClass:[NSArray class]]) {
RCTLogError(@"Buffer must be an instance of NSArray, got %@", NSStringFromClass([buffer class]));
return;
}
NSArray *requestsArray = (NSArray *)buffer;
NSUInteger bufferRowCount = [requestsArray count];
NSUInteger expectedFieldsCount = RCTBridgeFieldResponseReturnValues + 1;
@ -619,7 +700,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
RCTLogError(@"Must pass all fields to buffer - expected %zd, saw %zd", expectedFieldsCount, bufferRowCount);
return;
}
for (NSUInteger fieldIndex = RCTBridgeFieldRequestModuleIDs; fieldIndex <= RCTBridgeFieldParamss; fieldIndex++) {
id field = [requestsArray objectAtIndex:fieldIndex];
if (![field isKindOfClass:[NSArray class]]) {
@ -627,18 +708,18 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
return;
}
}
NSArray *moduleIDs = requestsArray[RCTBridgeFieldRequestModuleIDs];
NSArray *methodIDs = requestsArray[RCTBridgeFieldMethodIDs];
NSArray *paramsArrays = requestsArray[RCTBridgeFieldParamss];
NSUInteger numRequests = [moduleIDs count];
BOOL allSame = numRequests == [methodIDs count] && numRequests == [paramsArrays count];
if (!allSame) {
RCTLogError(@"Invalid data message - all must be length: %zd", numRequests);
return;
}
for (NSUInteger i = 0; i < numRequests; i++) {
@autoreleasepool {
[self _handleRequestNumber:i
@ -647,9 +728,9 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
params:paramsArrays[i]];
}
}
// TODO: only used by RCTUIManager - can we eliminate this special case?
dispatch_async(_shadowQueue, ^{
dispatch_async(self.shadowQueue, ^{
for (id module in _modulesByID.allObjects) {
if ([module respondsToSelector:@selector(batchDidComplete)]) {
[module batchDidComplete];
@ -668,170 +749,42 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
return NO;
}
// Look up method
NSArray *methods = RCTExportedMethodsByModuleID()[moduleID];
if (methodID >= methods.count) {
RCTLogError(@"Unknown methodID: %zd for module: %zd (%@)", methodID, moduleID, RCTModuleNamesByID[moduleID]);
return NO;
}
RCTModuleMethod *method = methods[methodID];
NSUInteger methodArity = method.arity;
if (params.count != methodArity) {
RCTLogError(@"Expected %tu arguments but got %tu invoking %@.%@",
methodArity,
params.count,
RCTModuleNamesByID[moduleID],
method.JSMethodName);
return NO;
}
__weak RCTBridge *weakSelf = self;
dispatch_async(_shadowQueue, ^{
dispatch_async(self.shadowQueue, ^{
__strong RCTBridge *strongSelf = weakSelf;
if (!strongSelf.isValid) {
// strongSelf has been invalidated since the dispatch_async call and this
// invocation should not continue.
return;
}
// TODO: we should just store module instances by index, since that's how we look them up anyway
id target = strongSelf->_modulesByID[moduleID];
RCTAssert(target != nil, @"No module found for name '%@'", RCTModuleNamesByID[moduleID]);
SEL selector = method.selector;
NSMethodSignature *methodSignature = [target methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[invocation setArgument:&target atIndex:0];
[invocation setArgument:&selector atIndex:1];
// Retain used blocks until after invocation completes.
NS_VALID_UNTIL_END_OF_SCOPE NSMutableArray *blocks = [NSMutableArray array];
[params enumerateObjectsUsingBlock:^(id param, NSUInteger idx, BOOL *stop) {
if ([param isEqual:[NSNull null]]) {
param = nil;
} else if ([method.blockArgumentIndexes containsIndex:idx]) {
id block = [strongSelf createResponseSenderBlock:[param integerValue]];
[blocks addObject:block];
param = block;
}
NSUInteger argIdx = idx + 2;
// TODO: can we do this lookup in advance and cache the logic instead of
// recalculating it every time for every parameter?
BOOL shouldSet = YES;
const char *argumentType = [methodSignature getArgumentTypeAtIndex:argIdx];
switch (argumentType[0]) {
case ':':
if ([param isKindOfClass:[NSString class]]) {
SEL sel = NSSelectorFromString(param);
[invocation setArgument:&sel atIndex:argIdx];
shouldSet = NO;
}
break;
case '*':
if ([param isKindOfClass:[NSString class]]) {
const char *string = [param UTF8String];
[invocation setArgument:&string atIndex:argIdx];
shouldSet = NO;
}
break;
// TODO: it seems like an error if the param doesn't respond
// so we should probably surface that error rather than failing silently
#define CASE(_value, _type, _selector) \
case _value: \
if ([param respondsToSelector:@selector(_selector)]) { \
_type value = [param _selector]; \
[invocation setArgument:&value atIndex:argIdx]; \
shouldSet = NO; \
} \
break;
CASE('c', char, charValue)
CASE('C', unsigned char, unsignedCharValue)
CASE('s', short, shortValue)
CASE('S', unsigned short, unsignedShortValue)
CASE('i', int, intValue)
CASE('I', unsigned int, unsignedIntValue)
CASE('l', long, longValue)
CASE('L', unsigned long, unsignedLongValue)
CASE('q', long long, longLongValue)
CASE('Q', unsigned long long, unsignedLongLongValue)
CASE('f', float, floatValue)
CASE('d', double, doubleValue)
CASE('B', BOOL, boolValue)
default:
break;
}
if (shouldSet) {
[invocation setArgument:&param atIndex:argIdx];
}
}];
// Look up module
id module = strongSelf->_modulesByID[moduleID];
if (!module) {
RCTLogError(@"No module found for name '%@'", RCTModuleNamesByID[moduleID]);
return;
}
@try {
[invocation invoke];
[method invokeWithBridge:strongSelf module:module arguments:params];
}
@catch (NSException *exception) {
RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, target, params, exception);
RCTLogError(@"Exception thrown while invoking %@ on target %@ with params %@: %@", method.JSMethodName, module, params, exception);
}
});
return YES;
}
/**
* Returns a callback that reports values back to the JS thread.
* TODO (#5906496): These responses should go into their own queue `MessageQueue.m` that
* mirrors the JS queue and protocol. For now, we speak the "language" of the JS
* queue by packing it into an array that matches the wire protocol.
*/
- (RCTResponseSenderBlock)createResponseSenderBlock:(NSInteger)cbID
{
if (!cbID) {
return nil;
}
return ^(NSArray *args) {
[self _sendResponseToJavaScriptCallbackID:cbID args:args];
};
}
+ (NSInvocation *)invocationForAdditionalArguments:(NSUInteger)argCount
{
static NSMutableDictionary *invocations;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
invocations = [NSMutableDictionary dictionary];
});
id key = @(argCount);
NSInvocation *invocation = invocations[key];
if (invocation == nil) {
NSString *objCTypes = [@"v@:" stringByPaddingToLength:3 + argCount withString:@"@" startingAtIndex:0];
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:objCTypes.UTF8String];
invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
invocations[key] = invocation;
}
return invocation;
}
- (void)registerRootView:(RCTRootView *)rootView
{
// TODO: only used by RCTUIManager - can we eliminate this special case?
for (id module in _modulesByID.allObjects) {
if ([module respondsToSelector:@selector(registerRootView:)]) {
[module registerRootView:rootView];
}
}
}
+ (BOOL)hasValidJSExecutor
{
return (_latestJSExecutor != nil && [_latestJSExecutor isValid]);
@ -844,7 +797,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
return;
}
NSMutableArray *args = [NSMutableArray arrayWithObject:level];
// TODO (#5906496): Find out and document why we skip the first object
for (id ob in [objects subarrayWithRange:(NSRange){1, [objects count] - 1}]) {
if ([NSJSONSerialization isValidJSONObject:@[ob]]) {
@ -853,11 +806,12 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
[args addObject:[ob description]];
}
}
// Note the js executor could get invalidated while we're trying to call this...need to watch out for that.
// Note: the js executor could get invalidated while we're trying to call this...need to watch out for that.
[_latestJSExecutor executeJSCall:@"RCTLog"
method:@"logIfNoNativeHook"
arguments:args
callback:^(id objcValue, NSError *error) {}];
callback:^(id json, NSError *error) {}];
}
@end

View File

@ -19,10 +19,12 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
@optional
/**
* Optional initializer for modules that require access
* to bridge features, such as sending events or making JS calls
* A reference to the RCTBridge. Useful for modules that require access
* to bridge features, such as sending events or making JS calls. This
* will be set automatically by the bridge when it initializes the module.
* To implement this in your module, just add @synthesize bridge = _bridge;
*/
- (instancetype)initWithBridge:(RCTBridge *)bridge;
@property (nonatomic, strong) RCTBridge *bridge;
/**
* The module name exposed to JS. If omitted, this will be inferred
@ -42,17 +44,11 @@ typedef void (^RCTResponseSenderBlock)(NSArray *response);
/**
* Injects constants into JS. These constants are made accessible via
* NativeModules.moduleName.X. Note that this method is not inherited when you
* subclass a module, and you should not call [super constantsToExport] when
* implementing it.
*/
+ (NSDictionary *)constantsToExport;
/**
* Some "constants" are not really constant, and need to be re-generated
* each time the bridge module is created. Support for this feature is
* deprecated and may be going away or changing, but for now you can use
* the -constantsToExport instance method to register these "pseudo-constants".
* NativeModules.ModuleName.X. This method is called when the module is
* registered by the bridge. It is only called once for the lifetime of the
* bridge, so it is not suitable for returning dynamic values, but may be
* used for long-lived values such as session keys, that are regenerated only
* as part of a reload of the entire React application.
*/
- (NSDictionary *)constantsToExport;

View File

@ -5,7 +5,7 @@
#import "RCTInvalidating.h"
typedef void (^RCTJavaScriptCompleteBlock)(NSError *error);
typedef void (^RCTJavaScriptCallback)(id objcValue, NSError *error);
typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
/**
* Abstracts away a JavaScript execution context - we may be running code in a

View File

@ -20,28 +20,22 @@
// If defined, only log messages that match this regex will fatal
#define RCTLOG_FATAL_REGEX nil
#define _RCTLog(__RCTLog__level, ...) do { \
NSString *__RCTLog__levelStr; \
switch(__RCTLog__level) { \
case RCTLOG_INFO: __RCTLog__levelStr = @"info"; break; \
case RCTLOG_WARN: __RCTLog__levelStr = @"warn"; break; \
case RCTLOG_ERROR: __RCTLog__levelStr = @"error"; break; \
case RCTLOG_MUSTFIX: __RCTLog__levelStr = @"mustfix"; break; \
} \
NSString *__RCTLog__msg = _RCTLogObjects(RCTLogFormat(__VA_ARGS__), __RCTLog__levelStr); \
if (__RCTLog__level >= RCTLOG_FATAL_LEVEL) { \
BOOL __RCTLog__fail = YES; \
if (RCTLOG_FATAL_REGEX) { \
NSError *__RCTLog__e; \
NSRegularExpression *__RCTLog__regex = [NSRegularExpression regularExpressionWithPattern:RCTLOG_FATAL_REGEX options:0 error:&__RCTLog__e]; \
__RCTLog__fail = [__RCTLog__regex numberOfMatchesInString:__RCTLog__msg options:0 range:NSMakeRange(0, [__RCTLog__msg length])] > 0; \
} \
RCTCAssert(!__RCTLog__fail, @"RCTLOG_FATAL_LEVEL %@: %@", __RCTLog__levelStr, __RCTLog__msg); \
} \
if (__RCTLog__level >= RCTLOG_REDBOX_LEVEL) { \
RCTRedBox *__RCTLog__redBox = [RCTRedBox sharedInstance]; \
[__RCTLog__redBox showErrorMessage:__RCTLog__msg]; \
} \
extern __unsafe_unretained NSString *RCTLogLevels[];
#define _RCTLog(_level, ...) do { \
NSString *__RCTLog__levelStr = RCTLogLevels[_level - 1]; \
NSString *__RCTLog__msg = RCTLogObjects(RCTLogFormat(__FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__), __RCTLog__levelStr); \
if (_level >= RCTLOG_FATAL_LEVEL) { \
BOOL __RCTLog__fail = YES; \
if (RCTLOG_FATAL_REGEX) { \
NSRegularExpression *__RCTLog__regex = [NSRegularExpression regularExpressionWithPattern:RCTLOG_FATAL_REGEX options:0 error:NULL]; \
__RCTLog__fail = [__RCTLog__regex numberOfMatchesInString:__RCTLog__msg options:0 range:NSMakeRange(0, [__RCTLog__msg length])] > 0; \
} \
RCTCAssert(!__RCTLog__fail, @"RCTLOG_FATAL_LEVEL %@: %@", __RCTLog__levelStr, __RCTLog__msg); \
} \
if (_level >= RCTLOG_REDBOX_LEVEL) { \
[[RCTRedBox sharedInstance] showErrorMessage:__RCTLog__msg]; \
} \
} while (0)
#define RCTLog(...) _RCTLog(RCTLOG_INFO, __VA_ARGS__)
@ -50,20 +44,15 @@
#define RCTLogError(...) _RCTLog(RCTLOG_ERROR, __VA_ARGS__)
#define RCTLogMustFix(...) _RCTLog(RCTLOG_MUSTFIX, __VA_ARGS__)
#define RCTLogFormat(...) _RCTLogFormat(__FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__)
#define RCTLogFormatString(...) _RCTLogFormatString(__FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__)
#ifdef __cplusplus
extern "C" {
#endif
NSString *_RCTLogObjects(NSArray *objects, NSString *level);
NSArray *_RCTLogFormat(const char *file, int lineNumber, const char *funcName, NSString *format, ...) NS_FORMAT_FUNCTION(4,5);
NSString *_RCTLogFormatString(const char *file, int lineNumber, const char *funcName, NSString *format, ...) NS_FORMAT_FUNCTION(4,5);
NSString *RCTLogObjects(NSArray *objects, NSString *level);
NSArray *RCTLogFormat(const char *file, int lineNumber, const char *funcName, NSString *format, ...) NS_FORMAT_FUNCTION(4,5);
void RCTInjectLogFunction(void (^logFunction)(NSString *msg));
#ifdef __cplusplus
}
#endif
typedef void (^RCTLogFunction)(NSString *format, NSString *str);
void RCTInjectLogFunction(RCTLogFunction func);

View File

@ -4,10 +4,17 @@
#import "RCTBridge.h"
static RCTLogFunction injectedLogFunction;
__unsafe_unretained NSString *RCTLogLevels[] = {
@"info",
@"warn",
@"error",
@"mustfix"
};
void RCTInjectLogFunction(RCTLogFunction func) {
injectedLogFunction = func;
static void (^RCTInjectedLogFunction)(NSString *msg);
void RCTInjectLogFunction(void (^logFunction)(NSString *msg)) {
RCTInjectedLogFunction = logFunction;
}
static inline NSString *_RCTLogPreamble(const char *file, int lineNumber, const char *funcName)
@ -21,7 +28,7 @@ static inline NSString *_RCTLogPreamble(const char *file, int lineNumber, const
}
// TODO (#5906496): // kinda ugly that this is tied to RCTBridge
NSString *_RCTLogObjects(NSArray *objects, NSString *level)
NSString *RCTLogObjects(NSArray *objects, NSString *level)
{
NSString *str = objects[0];
#if TARGET_IPHONE_SIMULATOR
@ -33,8 +40,8 @@ NSString *_RCTLogObjects(NSArray *objects, NSString *level)
{
// Print normal errors with timestamps when not in simulator.
// Non errors are already compiled out above, so log as error here.
if (injectedLogFunction) {
injectedLogFunction(@">\n %@", str);
if (RCTInjectedLogFunction) {
RCTInjectedLogFunction(str);
} else {
NSLog(@">\n %@", str);
}
@ -42,14 +49,14 @@ NSString *_RCTLogObjects(NSArray *objects, NSString *level)
return str;
}
// Returns array of objects. First arg is a simple string to print, remaining args are objects to pass through to the debugger so they are
// inspectable in the console.
NSArray *_RCTLogFormat(const char *file, int lineNumber, const char *funcName, NSString *format, ...)
// Returns array of objects. First arg is a simple string to print, remaining args
// are objects to pass through to the debugger so they are inspectable in the console.
NSArray *RCTLogFormat(const char *file, int lineNumber, const char *funcName, NSString *format, ...)
{
va_list args;
va_start(args, format);
NSString *preamble = _RCTLogPreamble(file, lineNumber, funcName);
// Pull out NSObjects so we can pass them through as inspectable objects to the js debugger
NSArray *formatParts = [format componentsSeparatedByString:@"%"];
NSMutableArray *objects = [NSMutableArray arrayWithObject:preamble];
@ -77,12 +84,3 @@ NSArray *_RCTLogFormat(const char *file, int lineNumber, const char *funcName, N
[objectsOut addObjectsFromArray:objects];
return objectsOut;
}
NSString *_RCTLogFormatString(const char *file, int lineNumber, const char *funcName, NSString *format, ...)
{
va_list args;
va_start (args, format);
NSString *body = [[NSString alloc] initWithFormat:format arguments:args];
va_end (args);
return [NSString stringWithFormat:@"%@ %@", _RCTLogPreamble(file, lineNumber, funcName), body];
}

View File

@ -2,7 +2,6 @@
#import "RCTRedBox.h"
#import "RCTRootView.h"
#import "RCTUtils.h"
@interface RCTRedBoxWindow : UIWindow <UITableViewDelegate, UITableViewDataSource>
@ -13,31 +12,31 @@
{
UIView *_rootView;
UITableView *_stackTraceTableView;
NSString *_lastErrorMessage;
NSArray *_lastStackTrace;
UITableViewCell *_cachedMessageCell;
}
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
self.windowLevel = UIWindowLevelStatusBar + 5;
self.backgroundColor = [UIColor colorWithRed:0.8 green:0 blue:0 alpha:1];
self.hidden = YES;
UIViewController *rootController = [[UIViewController alloc] init];
self.rootViewController = rootController;
_rootView = rootController.view;
_rootView.backgroundColor = [UIColor clearColor];
const CGFloat buttonHeight = 60;
CGRect detailsFrame = _rootView.bounds;
detailsFrame.size.height -= buttonHeight;
_stackTraceTableView = [[UITableView alloc] initWithFrame:detailsFrame style:UITableViewStylePlain];
_stackTraceTableView.delegate = self;
_stackTraceTableView.dataSource = self;
@ -45,21 +44,21 @@
_stackTraceTableView.separatorColor = [[UIColor whiteColor] colorWithAlphaComponent:0.3];
_stackTraceTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
[_rootView addSubview:_stackTraceTableView];
UIButton *dismissButton = [UIButton buttonWithType:UIButtonTypeCustom];
dismissButton.titleLabel.font = [UIFont systemFontOfSize:14];
[dismissButton setTitle:@"Dismiss (ESC)" forState:UIControlStateNormal];
[dismissButton setTitleColor:[[UIColor whiteColor] colorWithAlphaComponent:0.5] forState:UIControlStateNormal];
[dismissButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
[dismissButton addTarget:self action:@selector(dismiss) forControlEvents:UIControlEventTouchUpInside];
UIButton *reloadButton = [UIButton buttonWithType:UIButtonTypeCustom];
reloadButton.titleLabel.font = [UIFont systemFontOfSize:14];
[reloadButton setTitle:@"Reload JS (\u2318R)" forState:UIControlStateNormal];
[reloadButton setTitleColor:[[UIColor whiteColor] colorWithAlphaComponent:0.5] forState:UIControlStateNormal];
[reloadButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
[reloadButton addTarget:self action:@selector(reload) forControlEvents:UIControlEventTouchUpInside];
CGFloat buttonWidth = self.bounds.size.width / 2;
dismissButton.frame = CGRectMake(0, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight);
reloadButton.frame = CGRectMake(buttonWidth, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight);
@ -87,7 +86,7 @@
{
_lastStackTrace = stack;
_lastErrorMessage = message;
if (self.hidden && shouldShow) {
_cachedMessageCell = [self reuseCell:nil forErrorMessage:message];
@ -109,7 +108,7 @@
- (void)reload
{
[RCTRootView reloadAll];
[[NSNotificationCenter defaultCenter] postNotificationName:@"RCTReloadNotification" object:nil userInfo:nil];
[self dismiss];
}
@ -149,9 +148,9 @@
cell.backgroundColor = [UIColor clearColor];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
cell.textLabel.text = message;
return cell;
}
@ -168,7 +167,7 @@
cell.selectedBackgroundView = [[UIView alloc] init];
cell.selectedBackgroundView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.2];
}
cell.textLabel.text = stackFrame[@"methodName"];
cell.detailTextLabel.text = cell.detailTextLabel.text = [NSString stringWithFormat:@"%@:%@", [stackFrame[@"file"] lastPathComponent], stackFrame[@"lineNumber"]];
return cell;
@ -179,7 +178,7 @@
if ([indexPath section] == 0) {
NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
NSDictionary *attributes = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:16],
NSParagraphStyleAttributeName: paragraphStyle};
CGRect boundingRect = [_lastErrorMessage boundingRectWithSize:CGSizeMake(tableView.frame.size.width - 30, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil];
@ -206,14 +205,14 @@
// NOTE: We could use RCTKeyCommands for this, but since
// we control this window, we can use the standard, non-hacky
// mechanism instead
return @[
// Dismiss red box
[UIKeyCommand keyCommandWithInput:UIKeyInputEscape
modifierFlags:0
action:@selector(dismiss)],
// Reload
[UIKeyCommand keyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
@ -269,12 +268,12 @@
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack showIfHidden:(BOOL)shouldShow
{
#if DEBUG
dispatch_block_t block = ^{
if (!_window) {
_window = [[RCTRedBoxWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
_window = [[RCTRedBoxWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
}
[_window showErrorMessage:message withStack:stack showIfHidden:shouldShow];
};
@ -283,9 +282,9 @@
} else {
dispatch_async(dispatch_get_main_queue(), block);
}
#endif
}
- (void)dismiss

View File

@ -14,13 +14,13 @@
#import "RCTWebViewExecutor.h"
#import "UIView+ReactKit.h"
NSString *const RCTRootViewReloadNotification = @"RCTRootViewReloadNotification";
NSString *const RCTReloadNotification = @"RCTReloadNotification";
@implementation RCTRootView
{
RCTBridge *_bridge;
RCTTouchHandler *_touchHandler;
id <RCTJavaScriptExecutor> _executor;
id<RCTJavaScriptExecutor> _executor;
}
static Class _globalExecutorClass;
@ -77,7 +77,7 @@ static Class _globalExecutorClass;
// Add reload observer
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(reload)
name:RCTRootViewReloadNotification
name:RCTReloadNotification
object:nil];
}
@ -95,6 +95,10 @@ static Class _globalExecutorClass;
[_bridge enqueueJSCall:@"ReactIOS.unmountComponentAtNodeAndRemoveContainer"
args:@[self.reactTag]];
// TODO: eventually we'll want to be able to share the bridge between
// multiple rootviews, in which case we'll need to move this elsewhere
[_bridge invalidate];
}
- (void)bundleFinishedLoading:(NSError *)error
@ -108,7 +112,7 @@ static Class _globalExecutorClass;
}
} else {
[_bridge registerRootView:self];
[_bridge.uiManager registerRootView:self];
NSString *moduleName = _moduleName ?: @"";
NSDictionary *appParameters = @{
@ -137,7 +141,7 @@ static Class _globalExecutorClass;
// Choose local executor if specified, followed by global, followed by default
_executor = [[_executorClass ?: _globalExecutorClass ?: [RCTContextExecutor class] alloc] init];
_bridge = [[RCTBridge alloc] initWithJavaScriptExecutor:_executor moduleProvider:nil];
_bridge = [[RCTBridge alloc] initWithExecutor:_executor moduleProvider:nil];
_touchHandler = [[RCTTouchHandler alloc] initWithBridge:_bridge];
[self addGestureRecognizer:_touchHandler];
@ -229,7 +233,7 @@ static Class _globalExecutorClass;
+ (void)reloadAll
{
[[NSNotificationCenter defaultCenter] postNotificationName:RCTRootViewReloadNotification object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTReloadNotification object:nil];
}
- (void)startOrResetInteractionTiming

View File

@ -8,7 +8,7 @@
@interface RCTTouchHandler : UIGestureRecognizer<RCTInvalidating>
- (instancetype)initWithBridge:(RCTBridge *)bridge;
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
- (void)startOrResetInteractionTiming;
- (NSDictionary *)endAndResetInteractionTiming;

View File

@ -67,11 +67,6 @@
NSMutableArray *_bridgeInteractionTiming;
}
- (instancetype)initWithTarget:(id)target action:(SEL)action
{
RCT_NOT_DESIGNATED_INITIALIZER();
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if ((self = [super initWithTarget:nil action:NULL])) {

View File

@ -1,17 +1,15 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <tgmath.h>
#import <CoreGraphics/CoreGraphics.h>
#import <Foundation/Foundation.h>
#import <tgmath.h>
#import "RCTAssert.h"
// Macro to indicate when inherited initializer is not to be used
#define RCT_NOT_DESIGNATED_INITIALIZER() \
do { \
RCTAssert(NO, @"%@ is not the designated initializer for instances of %@.", NSStringFromSelector(_cmd), [self class]); \
return nil; \
} while (0)
#ifdef __cplusplus
extern "C" {
#endif
// Utility functions for JSON object <-> string serialization/deserialization
NSString *RCTJSONStringify(id jsonObject, NSError **error);
@ -39,3 +37,10 @@ void RCTSwapInstanceMethods(Class cls, SEL original, SEL replacement);
// Module subclass support
BOOL RCTClassOverridesClassMethod(Class cls, SEL selector);
BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector);
// Enumerate all classes that conform to NSObject protocol
void RCTEnumerateClasses(void (^block)(Class cls));
#ifdef __cplusplus
}
#endif

View File

@ -155,6 +155,31 @@ BOOL RCTClassOverridesInstanceMethod(Class cls, SEL selector)
return YES;
}
}
free(methods);
return NO;
}
void RCTEnumerateClasses(void (^block)(Class cls))
{
static Class *classes;
static unsigned int classCount;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
classes = objc_copyClassList(&classCount);
});
for (unsigned int i = 0; i < classCount; i++)
{
Class cls = classes[i];
Class superclass = cls;
while (superclass)
{
if (class_conformsToProtocol(superclass, @protocol(NSObject)))
{
block(cls);
break;
}
superclass = class_getSuperclass(superclass);
}
}
}

View File

@ -8,7 +8,7 @@
*/
@protocol RCTViewNodeProtocol <NSObject>
@property (nonatomic, strong) NSNumber *reactTag;
@property (nonatomic, copy) NSNumber *reactTag;
- (void)insertReactSubview:(id<RCTViewNodeProtocol>)subview atIndex:(NSInteger)atIndex;
- (void)removeReactSubview:(id<RCTViewNodeProtocol>)subview;
@ -21,6 +21,8 @@
@optional
// TODO: Deprecate this
// This method is called after layout has been performed for all views known
// to the RCTViewManager. It is only called on UIViews, not shadow views.
- (void)reactBridgeDidFinishTransaction;
@end

View File

@ -45,7 +45,7 @@ static JSValueRef RCTNativeLoggingHook(JSContextRef context, JSObjectRef object,
fprintf(stderr, "%s\n", [modifiedString UTF8String]); // don't print timestamps and other junk
#else
// Print normal errors with timestamps to files when not in simulator.
_RCTLogObjects(@[modifiedString], @"log");
RCTLogObjects(@[modifiedString], @"log");
#endif
JSStringRelease(string);
}

View File

@ -48,10 +48,10 @@
NSArray *buttons = args[@"buttons"];
if (!title && !message) {
RCTLogMustFix(@"Must specify either an alert title, or message, or both");
RCTLogError(@"Must specify either an alert title, or message, or both");
return;
} else if (buttons.count == 0) {
RCTLogMustFix(@"Must have at least one button.");
RCTLogError(@"Must have at least one button.");
return;
}
@ -68,7 +68,7 @@
NSInteger index = 0;
for (NSDictionary *button in buttons) {
if (button.count != 1) {
RCTLogMustFix(@"Button definitions should have exactly one key.");
RCTLogError(@"Button definitions should have exactly one key.");
}
NSString *buttonKey = [button.allKeys firstObject];
NSString *buttonTitle = [button[buttonKey] description];

View File

@ -5,6 +5,9 @@
#import <CoreLocation/CLLocationManager.h>
#import <CoreLocation/CLLocationManagerDelegate.h>
#import <FBReactKit/RCTAssert.h>
#import <FBReactKit/RCTLog.h>
#import "RCTAssert.h"
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
@ -29,17 +32,17 @@ const CLLocationAccuracy RCTLocationAccuracy = 500.0; // meters
@implementation RCTLocationObserver
{
CLLocationManager *_locationManager;
RCTEventDispatcher *_eventDispatcher;
NSDictionary *_lastLocationEvent;
NSMutableDictionary *_pendingRequests;
}
@synthesize bridge = _bridge;
#pragma mark - Lifecycle
- (instancetype)initWithBridge:(RCTBridge *)bridge
- (instancetype)init
{
if (self = [super init]) {
_eventDispatcher = bridge.eventDispatcher;
if ((self = [super init])) {
_pendingRequests = [[NSMutableDictionary alloc] init];
}
return self;
@ -107,7 +110,7 @@ const CLLocationAccuracy RCTLocationAccuracy = 500.0; // meters
},
@"timestamp": @(CACurrentMediaTime())
};
[_eventDispatcher sendDeviceEventWithName:@"geoLocationDidChange" body:event];
[_bridge.eventDispatcher sendDeviceEventWithName:@"geoLocationDidChange" body:event];
NSArray *pendingRequestsCopy;
// TODO (#5906496): is this locking neccessary? If so, use something better than @synchronize

View File

@ -49,7 +49,7 @@ static BOOL RCTViewControllerBasedStatusBarAppearance()
});
}
+ (NSDictionary *)constantsToExport
- (NSDictionary *)constantsToExport
{
return @{
@"Style": @{

View File

@ -51,35 +51,35 @@
@implementation RCTTiming
{
RCTSparseArray *_timers;
RCTBridge *_bridge;
id _updateTimer;
}
@synthesize bridge = _bridge;
+ (NSArray *)JSMethods
{
return @[@"RCTJSTimers.callTimers"];
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
- (instancetype)init
{
if ((self = [super init])) {
_bridge = bridge;
_timers = [[RCTSparseArray alloc] init];
[self startTimers];
for (NSString *name in @[UIApplicationWillResignActiveNotification,
UIApplicationDidEnterBackgroundNotification,
UIApplicationWillTerminateNotification]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(stopTimers)
name:name
object:nil];
}
for (NSString *name in @[UIApplicationDidBecomeActiveNotification,
UIApplicationWillEnterForegroundNotification]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(startTimers)
name:name
@ -114,14 +114,14 @@
- (void)startTimers
{
RCTAssertMainThread();
if (![self isValid] || _updateTimer != nil) {
if (![self isValid] || _updateTimer != nil || _timers.count == 0) {
return;
}
_updateTimer = [CADisplayLink displayLinkWithTarget:self selector:@selector(update)];
if (_updateTimer) {
[_updateTimer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[_updateTimer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
} else {
RCTLogWarn(@"Failed to create a display link (probably on buildbot) - using an NSTimer for AppEngine instead.");
_updateTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60)
@ -135,7 +135,7 @@
- (void)update
{
RCTAssertMainThread();
NSMutableArray *timersToCall = [[NSMutableArray alloc] init];
for (RCTTimer *timer in _timers.allObjects) {
if ([timer updateFoundNeedsJSUpdate]) {
@ -145,7 +145,7 @@
_timers[timer.callbackID] = nil;
}
}
// call timers that need to be called
if ([timersToCall count] > 0) {
[_bridge enqueueJSCall:@"RCTJSTimers.callTimers" args:@[timersToCall]];
@ -185,6 +185,7 @@
repeats:repeats];
dispatch_async(dispatch_get_main_queue(), ^{
_timers[callbackID] = timer;
[self startTimers];
});
}
@ -195,6 +196,9 @@
if (timerID) {
dispatch_async(dispatch_get_main_queue(), ^{
_timers[timerID] = nil;
if (_timers.count == 0) {
[self stopTimers];
}
});
} else {
RCTLogWarn(@"Called deleteTimer: with a nil timerID");

View File

@ -2,13 +2,18 @@
#import <UIKit/UIKit.h>
#import "RCTBridge.h"
#import "RCTBridgeModule.h"
#import "RCTInvalidating.h"
#import "RCTViewManager.h"
@class RCTRootView;
@protocol RCTScrollableProtocol;
/**
* The RCTUIManager is the module responsible for updating the view hierarchy.
*/
@interface RCTUIManager : NSObject <RCTBridgeModule, RCTInvalidating>
@property (nonatomic, weak) id<RCTScrollableProtocol> mainScrollView;
@ -19,8 +24,33 @@
*/
@property (nonatomic, readwrite, weak) id<UIScrollViewDelegate> nativeMainScrollDelegate;
/**
* Register a root view with the RCTUIManager. Theoretically, a single manager
* can support multiple root views, however this feature is not currently exposed
* and may eventually be removed.
*/
- (void)registerRootView:(RCTRootView *)rootView;
/**
* Schedule a block to be executed on the UI thread. Useful if you need to execute
* view logic after all currently queued view updates have completed.
*/
- (void)addUIBlock:(RCTViewManagerUIBlock)block;
/**
* The view that is currently first responder, according to the JS context.
*/
+ (UIView *)JSResponder;
@end
/**
* This category makes the current RCTUIManager instance available via the
* RCTBridge, which is useful for RCTBridgeModules or RCTViewManagers that
* need to access the RCTUIManager.
*/
@interface RCTBridge (RCTUIManager)
@property (nonatomic, readonly) RCTUIManager *uiManager;
@end

View File

@ -32,47 +32,6 @@ static void RCTTraverseViewNodes(id<RCTViewNodeProtocol> view, react_view_node_b
}
}
static NSDictionary *RCTViewModuleClasses(void)
{
static NSMutableDictionary *modules;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
modules = [NSMutableDictionary dictionary];
unsigned int classCount;
Class *classes = objc_copyClassList(&classCount);
for (unsigned int i = 0; i < classCount; i++) {
Class cls = classes[i];
if (!class_getSuperclass(cls)) {
// Class has no superclass - it's probably something weird
continue;
}
if (![cls isSubclassOfClass:[RCTViewManager class]]) {
// Not a view module
continue;
}
// Get module name
NSString *moduleName = [cls moduleName];
// Check module name is unique
id existingClass = modules[moduleName];
RCTCAssert(existingClass == Nil, @"Attempted to register view module class %@ "
"for the name '%@', but name was already registered by class %@", cls, moduleName, existingClass);
// Add to module list
modules[moduleName] = cls;
}
free(classes);
});
return modules;
}
@interface RCTAnimation : NSObject
@property (nonatomic, readonly) NSTimeInterval duration;
@ -190,48 +149,70 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
@implementation RCTUIManager
{
dispatch_queue_t _shadowQueue;
// Root views are only mutated on the shadow queue
NSMutableSet *_rootViewTags;
NSMutableArray *_pendingUIBlocks;
NSLock *_pendingUIBlocksLock;
// Animation
RCTLayoutAnimation *_nextLayoutAnimation; // RCT thread only
RCTLayoutAnimation *_layoutAnimation; // Main thread only
// Keyed by moduleName
// Keyed by viewName
NSMutableDictionary *_defaultShadowViews; // RCT thread only
NSMutableDictionary *_defaultViews; // Main thread only
NSDictionary *_viewManagers;
// Keyed by React tag
RCTSparseArray *_viewManagerRegistry; // RCT thread only
RCTSparseArray *_shadowViewRegistry; // RCT thread only
RCTSparseArray *_viewRegistry; // Main thread only
__weak RCTBridge *_bridge;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
@synthesize bridge =_bridge;
/**
* This function derives the view name automatically
* from the module name.
*/
static NSString *RCTViewNameForModuleName(NSString *moduleName)
{
NSString *name = moduleName;
if ([name hasSuffix:@"Manager"]) {
name = [name substringToIndex:name.length - @"Manager".length];
}
return name;
}
/**
* This private constructor should only be called when creating
* isolated UIImanager instances for testing. Normal initialization
* is via -init:, which is called automatically by the bridge.
*/
- (instancetype)initWithShadowQueue:(dispatch_queue_t)shadowQueue
{
if ((self = [self init])) {
_shadowQueue = shadowQueue;
_viewManagers = [[NSMutableDictionary alloc] init];
}
return self;
}
- (instancetype)init
{
if ((self = [super init])) {
_bridge = bridge;
_pendingUIBlocksLock = [[NSLock alloc] init];
// Instantiate view managers
NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init];
[RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, Class moduleClass, BOOL *stop) {
viewManagers[moduleName] = [[moduleClass alloc] initWithEventDispatcher:_bridge.eventDispatcher];
}];
_viewManagers = viewManagers;
_defaultShadowViews = [[NSMutableDictionary alloc] init];
_defaultViews = [[NSMutableDictionary alloc] init];
_viewManagerRegistry = [[RCTSparseArray alloc] init];
_shadowViewRegistry = [[RCTSparseArray alloc] init];
_viewRegistry = [[RCTSparseArray alloc] init];
// Internal resources
_pendingUIBlocks = [[NSMutableArray alloc] init];
_rootViewTags = [[NSMutableSet alloc] init];
@ -239,16 +220,35 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
return self;
}
- (instancetype)init
{
RCT_NOT_DESIGNATED_INITIALIZER();
}
- (void)dealloc
{
RCTAssert(!self.valid, @"must call -invalidate before -dealloc");
}
- (void)setBridge:(RCTBridge *)bridge
{
if (_bridge) {
// Clear previous bridge data
[self invalidate];
}
if (bridge) {
_bridge = bridge;
_shadowQueue = _bridge.shadowQueue;
// Get view managers from bridge
NSMutableDictionary *viewManagers = [[NSMutableDictionary alloc] init];
[_bridge.modules enumerateKeysAndObjectsUsingBlock:^(NSString *moduleName, RCTViewManager *manager, BOOL *stop) {
if ([manager isKindOfClass:[RCTViewManager class]]) {
viewManagers[RCTViewNameForModuleName(moduleName)] = manager;
}
}];
_viewManagers = [viewManagers copy];
}
}
- (BOOL)isValid
{
return _viewRegistry != nil;
@ -260,6 +260,7 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
_viewRegistry = nil;
_shadowViewRegistry = nil;
_bridge = nil;
[_pendingUIBlocksLock lock];
_pendingUIBlocks = nil;
@ -269,22 +270,22 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
- (void)registerRootView:(RCTRootView *)rootView;
{
RCTAssertMainThread();
NSNumber *reactTag = rootView.reactTag;
UIView *existingView = _viewRegistry[reactTag];
RCTCAssert(existingView == nil || existingView == rootView,
@"Expect all root views to have unique tag. Added %@ twice", reactTag);
// Register view
_viewRegistry[reactTag] = rootView;
CGRect frame = rootView.frame;
// Register manager (TODO: should we do this, or leave it nil?)
_viewManagerRegistry[reactTag] = _viewManagers[[RCTViewManager moduleName]];
_viewManagerRegistry[reactTag] = _viewManagers[@"View"];
// Register shadow view
dispatch_async(_bridge.shadowQueue, ^{
dispatch_async(_shadowQueue, ^{
RCTShadowView *shadowView = [[RCTShadowView alloc] init];
shadowView.reactTag = reactTag;
shadowView.frame = frame;
@ -315,7 +316,7 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
- (void)addUIBlock:(RCTViewManagerUIBlock)block
{
RCTAssert(![NSThread isMainThread], @"This method should only be called on the shadow thread");
__weak RCTUIManager *weakViewManager = self;
__weak RCTSparseArray *weakViewRegistry = _viewRegistry;
dispatch_block_t outerBlock = ^{
@ -333,6 +334,8 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
- (RCTViewManagerUIBlock)uiBlockWithLayoutUpdateForRootView:(RCTShadowView *)rootShadowView
{
RCTAssert(![NSThread isMainThread], @"This should never be executed on main thread.");
NSMutableSet *viewsWithNewFrames = [NSMutableSet setWithCapacity:1];
// This is nuanced. In the JS thread, we create a new update buffer
@ -340,7 +343,8 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
// these structures in the UI-thread block. `NSMutableArray` is not thread
// safe so we rely on the fact that we never mutate it after it's passed to
// the main thread.
[rootShadowView collectRootUpdatedFrames:viewsWithNewFrames parentConstraint:(CGSize){CSS_UNDEFINED, CSS_UNDEFINED}];
[rootShadowView collectRootUpdatedFrames:viewsWithNewFrames
parentConstraint:(CGSize){CSS_UNDEFINED, CSS_UNDEFINED}];
// Parallel arrays
NSMutableArray *frameReactTags = [NSMutableArray arrayWithCapacity:viewsWithNewFrames.count];
@ -354,12 +358,19 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
[areNew addObject:@(shadowView.isNewView)];
[parentsAreNew addObject:@(shadowView.superview.isNewView)];
}
for (RCTShadowView *shadowView in viewsWithNewFrames) {
// We have to do this after we build the parentsAreNew array.
shadowView.newView = NO;
}
NSMutableArray *updateBlocks = [[NSMutableArray alloc] init];
for (RCTShadowView *shadowView in viewsWithNewFrames) {
RCTViewManager *manager = _viewManagerRegistry[shadowView.reactTag];
RCTViewManagerUIBlock block = [manager uiBlockToAmendWithShadowView:shadowView];
if (block) [updateBlocks addObject:block];
}
// Perform layout (possibly animated)
NSNumber *rootViewTag = rootShadowView.reactTag;
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
@ -373,15 +384,16 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
// Convert the frame so it works with anchorPoint = center.
CGPoint position = {CGRectGetMidX(frame), CGRectGetMidY(frame)};
CGRect bounds = {0, 0, frame.size};
// Avoid crashes due to nan coords
if (isnan(position.x) || isnan(position.y) ||
isnan(bounds.origin.x) || isnan(bounds.origin.y) ||
isnan(bounds.size.width) || isnan(bounds.size.height)) {
RCTLogError(@"Invalid layout for (%zd)%@. position: %@. bounds: %@", [view reactTag], self, NSStringFromCGPoint(position), NSStringFromCGRect(bounds));
RCTLogError(@"Invalid layout for (%@)%@. position: %@. bounds: %@",
[view reactTag], self, NSStringFromCGPoint(position), NSStringFromCGRect(bounds));
continue;
}
void (^completion)(BOOL finished) = ^(BOOL finished) {
if (self->_layoutAnimation.callback) {
self->_layoutAnimation.callback(@[@(finished)]);
@ -395,14 +407,20 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
[updateAnimation performAnimations:^{
view.layer.position = position;
view.layer.bounds = bounds;
for (RCTViewManagerUIBlock block in updateBlocks) {
block(self, _viewRegistry);
}
} withCompletionBlock:completion];
} else {
view.layer.position = position;
view.layer.bounds = bounds;
for (RCTViewManagerUIBlock block in updateBlocks) {
block(self, _viewRegistry);
}
completion(YES);
}
// Animate view creations
// Animate view creation
BOOL shouldAnimateCreation = isNew && ![parentsAreNew[ii] boolValue];
RCTAnimation *createAnimation = _layoutAnimation.createAnimation;
if (shouldAnimateCreation && createAnimation) {
@ -417,12 +435,22 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
} else if ([createAnimation.property isEqual:@"opacity"]) {
view.layer.opacity = 1.0;
} else {
RCTLogError(@"Unsupported layout animation createConfig property %@", createAnimation.property);
RCTLogError(@"Unsupported layout animation createConfig property %@",
createAnimation.property);
}
for (RCTViewManagerUIBlock block in updateBlocks) {
block(self, _viewRegistry);
}
} withCompletionBlock:nil];
}
}
/**
* Enumerate all active (attached to a parent) views and call
* reactBridgeDidFinishTransaction on them if they implement it.
* TODO: this is quite inefficient. If this was handled via the
* ViewManager instead, it could be done more efficiently.
*/
RCTRootView *rootView = _viewRegistry[rootViewTag];
RCTTraverseViewNodes(rootView, ^(id<RCTViewNodeProtocol> view) {
if ([view respondsToSelector:@selector(reactBridgeDidFinishTransaction)]) {
@ -436,7 +464,7 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
{
NSMutableSet *applierBlocks = [NSMutableSet setWithCapacity:1];
[topView collectUpdatedProperties:applierBlocks parentProperties:@{}];
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
for (RCTApplierBlock block in applierBlocks) {
block(viewRegistry);
@ -454,7 +482,7 @@ UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimationType t
id<RCTViewNodeProtocol> container = _viewRegistry[containerID];
RCTAssert(container != nil, @"container view (for ID %@) not found", containerID);
NSUInteger subviewsCount = [[container reactSubviews] count];
NSMutableArray *indices = [[NSMutableArray alloc] initWithCapacity:subviewsCount];
for (NSInteger childIndex = 0; childIndex < subviewsCount; childIndex++) {
@ -616,11 +644,11 @@ static BOOL RCTCallPropertySetter(SEL setter, id value, id view, id defaultView,
{
// TODO: cache respondsToSelector tests
if ([manager respondsToSelector:setter]) {
if (value == [NSNull null]) {
value = nil;
}
((void (*)(id, SEL, id, id, id))objc_msgSend)(manager, setter, value, view, defaultView);
return YES;
}
@ -631,9 +659,9 @@ static void RCTSetViewProps(NSDictionary *props, UIView *view,
UIView *defaultView, RCTViewManager *manager)
{
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set_%@:forView:withDefaultView:", key]);
// For regular views we don't attempt to set properties
// unless the view property has been explicitly exported.
RCTCallPropertySetter(setter, obj, view, defaultView, manager);
@ -644,13 +672,13 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
RCTShadowView *defaultView, RCTViewManager *manager)
{
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set_%@:forShadowView:withDefaultView:", key]);
// For shadow views we call any custom setter methods by default,
// but if none is specified, we attempt to set property anyway.
if (!RCTCallPropertySetter(setter, obj, shadowView, defaultView, manager)) {
if (obj == [NSNull null]) {
// Copy property from default view to current
// Note: not just doing `[defaultView valueForKey:key]`, the
@ -661,20 +689,20 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
}
}
}];
// Update layout
[shadowView updateShadowViewLayout];
}
- (void)createAndRegisterViewWithReactTag:(NSNumber *)reactTag
moduleName:(NSString *)moduleName
viewName:(NSString *)viewName
props:(NSDictionary *)props
{
RCT_EXPORT(createView);
RCTViewManager *manager = _viewManagers[moduleName];
RCTViewManager *manager = _viewManagers[viewName];
if (manager == nil) {
RCTLogWarn(@"No manager class found for view with module name \"%@\"", moduleName);
RCTLogWarn(@"No manager class found for view with module name \"%@\"", viewName);
manager = [[RCTViewManager alloc] init];
}
@ -682,57 +710,57 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
_viewManagerRegistry[reactTag] = manager;
// Generate default view, used for resetting default props
if (!_defaultShadowViews[moduleName]) {
_defaultShadowViews[moduleName] = [manager shadowView];
if (!_defaultShadowViews[viewName]) {
_defaultShadowViews[viewName] = [manager shadowView];
}
RCTShadowView *shadowView = [manager shadowView];
shadowView.moduleName = moduleName;
shadowView.moduleName = viewName;
shadowView.reactTag = reactTag;
RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[moduleName], manager);
RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[viewName], manager);
_shadowViewRegistry[shadowView.reactTag] = shadowView;
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
RCTCAssertMainThread();
// Generate default view, used for resetting default props
if (!uiManager->_defaultViews[moduleName]) {
if (!uiManager->_defaultViews[viewName]) {
// Note the default is setup after the props are read for the first time ever
// for this className - this is ok because we only use the default for restoring
// defaults, which never happens on first creation.
uiManager->_defaultViews[moduleName] = [manager view];
uiManager->_defaultViews[viewName] = [manager view];
}
UIView *view = [manager view];
if (view) {
// Set required properties
view.reactTag = reactTag;
view.multipleTouchEnabled = YES;
view.userInteractionEnabled = YES; // required for touch handling
view.layer.allowsGroupOpacity = YES; // required for touch handling
// Set custom properties
RCTSetViewProps(props, view, uiManager->_defaultViews[moduleName], manager);
RCTSetViewProps(props, view, uiManager->_defaultViews[viewName], manager);
}
viewRegistry[view.reactTag] = view;
}];
}
// TODO: remove moduleName param as it isn't needed
- (void)updateView:(NSNumber *)reactTag moduleName:(__unused NSString *)_ props:(NSDictionary *)props
// TODO: remove viewName param as it isn't needed
- (void)updateView:(NSNumber *)reactTag viewName:(__unused NSString *)_ props:(NSDictionary *)props
{
RCT_EXPORT();
RCTViewManager *viewManager = _viewManagerRegistry[reactTag];
NSString *moduleName = [[viewManager class] moduleName];
NSString *viewName = RCTViewNameForModuleName([[viewManager class] moduleName]);
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[moduleName], viewManager);
RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[viewName], viewManager);
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
UIView *view = uiManager->_viewRegistry[reactTag];
RCTSetViewProps(props, view, uiManager->_defaultViews[moduleName], viewManager);
RCTSetViewProps(props, view, uiManager->_defaultViews[viewName], viewManager);
}];
}
@ -760,16 +788,15 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
- (void)batchDidComplete
{
// First copy the previous blocks into a temporary variable, then reset the
// pending blocks to a new array. This guards against mutation while
// processing the pending blocks in another thread.
// Gather blocks to be executed now that all view hierarchy manipulations have
// been completed (note that these may still take place before layout has finished)
for (RCTViewManager *manager in _viewManagers.allValues) {
RCTViewManagerUIBlock uiBlock = [manager uiBlockToAmendWithShadowViewRegistry:_shadowViewRegistry];
if (uiBlock) {
[self addUIBlock:uiBlock];
}
}
// Set up next layout animation
if (_nextLayoutAnimation) {
RCTLayoutAnimation *layoutAnimation = _nextLayoutAnimation;
@ -777,14 +804,14 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
uiManager->_layoutAnimation = layoutAnimation;
}];
}
// Perform layout
for (NSNumber *reactTag in _rootViewTags) {
RCTShadowView *rootView = _shadowViewRegistry[reactTag];
[self addUIBlock:[self uiBlockWithLayoutUpdateForRootView:rootView]];
[self _amendPendingUIBlocksWithStylePropagationUpdateForRootView:rootView];
}
// Clear layout animations
if (_nextLayoutAnimation) {
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
@ -792,12 +819,16 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
}];
_nextLayoutAnimation = nil;
}
// First copy the previous blocks into a temporary variable, then reset the
// pending blocks to a new array. This guards against mutation while
// processing the pending blocks in another thread.
[_pendingUIBlocksLock lock];
NSArray *previousPendingUIBlocks = _pendingUIBlocks;
_pendingUIBlocks = [[NSMutableArray alloc] init];
[_pendingUIBlocksLock unlock];
// Execute the previously queued UI blocks
dispatch_async(dispatch_get_main_queue(), ^{
for (dispatch_block_t block in previousPendingUIBlocks) {
block();
@ -828,7 +859,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
}
RCTCAssert([rootView isReactRootView], @"React view not inside RCTRootView");
// By convention, all coordinates, whether they be touch coordinates, or
// measurement coordinates are with respect to the root view.
CGPoint pagePoint = [view.superview convertPoint:frame.origin toView:rootView];
@ -844,37 +875,6 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
}];
}
- (void)requestSchedulingJavaScriptNavigation:(NSNumber *)reactTag
errorCallback:(RCTResponseSenderBlock)errorCallback
callback:(RCTResponseSenderBlock)callback
{
RCT_EXPORT();
if (!callback || !errorCallback) {
RCTLogError(@"Callback not provided for navigation scheduling.");
return;
}
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
if (reactTag) {
//TODO: This is nasty - why is RCTNavigator hard-coded?
id rkObject = viewRegistry[reactTag];
if ([rkObject isKindOfClass:[RCTNavigator class]]) {
RCTNavigator *navigator = (RCTNavigator *)rkObject;
BOOL wasAcquired = [navigator requestSchedulingJavaScriptNavigation];
callback(@[@(wasAcquired)]);
} else {
NSString *msg =
[NSString stringWithFormat: @"Cannot set lock: Tag %@ is not an RCTNavigator", reactTag];
errorCallback(@[RCTAPIErrorObject(msg)]);
}
} else {
NSString *msg = [NSString stringWithFormat: @"Tag not specified for requestSchedulingJavaScriptNavigation"];
errorCallback(@[RCTAPIErrorObject(msg)]);
}
}];
}
/**
* TODO: This could be modified to accept any `RCTViewNodeProtocol`, if
* appropriate changes were made to that protocol to support `superview`
@ -884,22 +884,20 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
+ (void)measureLayoutOnNodes:(RCTShadowView *)view
ancestor:(RCTShadowView *)ancestor
errorCallback:(RCTResponseSenderBlock)errorCallback
callback:(RCTResponseSenderBlock)callback
callback:(__unused RCTResponseSenderBlock)callback
{
if (!view) {
NSString *msg = [NSString stringWithFormat: @"Attempting to measure view that does not exist %@", view];
errorCallback(@[RCTAPIErrorObject(msg)]);
RCTLogError(@"Attempting to measure view that does not exist");
return;
}
if (!ancestor) {
NSString *msg = [NSString stringWithFormat: @"Attempting to measure relative to ancestor that does not exist %@", ancestor];
errorCallback(@[RCTAPIErrorObject(msg)]);
RCTLogError(@"Attempting to measure relative to ancestor that does not exist");
return;
}
CGRect result = [RCTShadowView measureLayout:view relativeTo:ancestor];
if (CGRectIsNull(result)) {
NSString *msg = [NSString stringWithFormat: @"view %@ is not an decendant of %@", view, ancestor];
errorCallback(@[RCTAPIErrorObject(msg)]);
RCTLogError(@"view %@ (tag #%@) is not a decendant of %@ (tag #%@)",
view, view.reactTag, ancestor, ancestor.reactTag);
return;
}
CGFloat leftOffset = result.origin.x;
@ -907,7 +905,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
CGFloat width = result.size.width;
CGFloat height = result.size.height;
if (isnan(leftOffset) || isnan(topOffset) || isnan(width) || isnan(height)) {
errorCallback(@[RCTAPIErrorObject(@"Attempted to measure layout but offset or dimensions were NaN")]);
RCTLogError(@"Attempted to measure layout but offset or dimensions were NaN");
return;
}
callback(@[@(topOffset), @(leftOffset), @(width), @(height)]);
@ -965,8 +963,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
RCTShadowView *shadowView = _shadowViewRegistry[reactTag];
if (!shadowView) {
NSString *msg = [NSString stringWithFormat: @"Attempting to measure view that does not exist %@", shadowView];
errorCallback(@[RCTAPIErrorObject(msg)]);
RCTLogError(@"Attempting to measure view that does not exist (tag #%@)", reactTag);
return;
}
NSArray *childShadowViews = [shadowView reactSubviews];
@ -977,8 +974,8 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
RCTShadowView *childShadowView = [childShadowViews objectAtIndex:ii];
CGRect childLayout = [RCTShadowView measureLayout:childShadowView relativeTo:shadowView];
if (CGRectIsNull(childLayout)) {
NSString *msg = [NSString stringWithFormat: @"view %@ is not a decendant of %@", childShadowView, shadowView];
errorCallback(@[RCTAPIErrorObject(msg)]);
RCTLogError(@"View %@ (tag #%@) is not a decendant of %@ (tag #%@)",
childShadowView, childShadowView.reactTag, shadowView, shadowView.reactTag);
return;
}
@ -1088,7 +1085,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
_jsResponder = viewRegistry[reactTag];
if (!_jsResponder) {
RCTLogMustFix(@"Invalid view set to be the JS responder - tag %zd", reactTag);
RCTLogError(@"Invalid view set to be the JS responder - tag %zd", reactTag);
}
}];
}
@ -1102,7 +1099,11 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
}];
}
+ (NSDictionary *)allBubblingEventTypesConfigs
// TODO: these event types should be distributed among the modules
// that declare them. Also, events should be registerable by any class
// that can call event handlers, not just UIViewManagers. This code
// also seems highly redundant - every event has the same properties.
- (NSDictionary *)customBubblingEventTypes
{
NSMutableDictionary *customBubblingEventTypesConfigs = [@{
// Bubble dispatched events
@ -1192,11 +1193,12 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
},
} mutableCopy];
[RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) {
[_viewManagers enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) {
if (RCTClassOverridesClassMethod(cls, @selector(customBubblingEventTypes))) {
NSDictionary *eventTypes = [cls customBubblingEventTypes];
for (NSString *eventName in eventTypes) {
RCTCAssert(!customBubblingEventTypesConfigs[eventName], @"Event '%@' registered multiple times.", eventName);
RCTCAssert(!customBubblingEventTypesConfigs[eventName],
@"Event '%@' registered multiple times.", eventName);
}
[customBubblingEventTypesConfigs addEntriesFromDictionary:eventTypes];
}
@ -1205,7 +1207,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
return customBubblingEventTypesConfigs;
}
+ (NSDictionary *)allDirectEventTypesConfigs
- (NSDictionary *)customDirectEventTypes
{
NSMutableDictionary *customDirectEventTypes = [@{
@"topScrollBeginDrag": @{
@ -1243,7 +1245,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
},
} mutableCopy];
[RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) {
[_viewManagers enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) {
if (RCTClassOverridesClassMethod(cls, @selector(customDirectEventTypes))) {
NSDictionary *eventTypes = [cls customDirectEventTypes];
for (NSString *eventName in eventTypes) {
@ -1256,11 +1258,11 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
return customDirectEventTypes;
}
+ (NSDictionary *)constantsToExport
- (NSDictionary *)constantsToExport
{
NSMutableDictionary *allJSConstants = [@{
@"customBubblingEventTypes": [self allBubblingEventTypesConfigs],
@"customDirectEventTypes": [self allDirectEventTypesConfigs],
@"customBubblingEventTypes": [self customBubblingEventTypes],
@"customDirectEventTypes": [self customDirectEventTypes],
@"NSTextAlignment": @{
@"Left": @(NSTextAlignmentLeft),
@"Center": @(NSTextAlignmentCenter),
@ -1320,15 +1322,15 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
},
} mutableCopy];
[RCTViewModuleClasses() enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) {
[_viewManagers enumerateKeysAndObjectsUsingBlock:^(NSString *name, Class cls, BOOL *stop) {
// TODO: should these be inherited?
NSDictionary *constants = RCTClassOverridesClassMethod(cls, @selector(constantsToExport)) ? [cls constantsToExport] : nil;
if ([constants count]) {
NSMutableDictionary *namespace = [NSMutableDictionary dictionaryWithDictionary:allJSConstants[name]];
RCTAssert(namespace[@"Constants"] == nil , @"Cannot redefine Constants in namespace: %@", name);
NSMutableDictionary *constantsNamespace = [NSMutableDictionary dictionaryWithDictionary:allJSConstants[name]];
RCTAssert(constantsNamespace[@"Constants"] == nil , @"Cannot redefine Constants in namespace: %@", name);
// add an additional 'Constants' namespace for each class
namespace[@"Constants"] = constants;
allJSConstants[name] = [namespace copy];
constantsNamespace[@"Constants"] = constants;
allJSConstants[name] = [constantsNamespace copy];
}
}];
@ -1342,10 +1344,11 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
RCT_EXPORT();
if (_nextLayoutAnimation) {
RCTLogWarn(@"Warning: Overriding previous layout animation with new one before the first began:\n%@ -> %@.", _nextLayoutAnimation, config);
RCTLogWarn(@"Warning: Overriding previous layout animation with new one before the first began:\n%@ -> %@.",
_nextLayoutAnimation, config);
}
if (config[@"delete"] != nil) {
RCTLogError(@"LayoutAnimation only supports create and update right now. Config: %@", config);
RCTLogError(@"LayoutAnimation only supports create and update right now. Config: %@", config);
}
_nextLayoutAnimation = [[RCTLayoutAnimation alloc] initWithDictionary:config callback:callback];
}
@ -1389,3 +1392,12 @@ static UIView *_jsResponder;
}
@end
@implementation RCTBridge (RCTUIManager)
- (RCTUIManager *)uiManager
{
return self.modules[NSStringFromClass([RCTUIManager class])];
}
@end

View File

@ -11,7 +11,7 @@
@property (nonatomic, strong) UIView *reactNavSuperviewLink;
@property (nonatomic, assign) NSInteger requestedTopOfStack;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
/**
* Schedules a JavaScript navigation and prevents `UIKit` from navigating until

View File

@ -126,21 +126,6 @@ NSInteger kNeverProgressed = -10000;
*/
@implementation RCTNavigationController
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
RCT_NOT_DESIGNATED_INITIALIZER();
}
- (instancetype)initWithNavigationBarClass:(Class)navigationBarClass toolbarClass:(Class)toolbarClass
{
RCT_NOT_DESIGNATED_INITIALIZER();
}
- (instancetype)initWithRootViewController:(UIViewController *)rootViewController
{
RCT_NOT_DESIGNATED_INITIALIZER();
}
/**
* @param callback Callback that is invoked when a "scroll" interaction begins
* so that `RCTNavigator` can notify `JavaScript`.
@ -275,11 +260,6 @@ NSInteger kNeverProgressed = -10000;
@implementation RCTNavigator
- (instancetype)initWithFrame:(CGRect)frame
{
RCT_NOT_DESIGNATED_INITIALIZER();
}
- (id)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super initWithFrame:CGRectZero])) {
@ -308,7 +288,6 @@ NSInteger kNeverProgressed = -10000;
[self addSubview:_navigationController.view];
[_navigationController.view addSubview:_dummyView];
}
return self;
}
@ -363,8 +342,8 @@ NSInteger kNeverProgressed = -10000;
(RCTWrapperViewController *)[context viewControllerForKey:UITransitionContextToViewControllerKey];
NSUInteger indexOfFrom = [_currentViews indexOfObject:fromController.navItem];
NSUInteger indexOfTo = [_currentViews indexOfObject:toController.navItem];
CGFloat destination = indexOfFrom < indexOfTo ? 1.0f : -1.0f;
_dummyView.frame = CGRectMake(destination, 0.0f, 0.0f, 0.0f);
CGFloat destination = indexOfFrom < indexOfTo ? 1.0 : -1.0;
_dummyView.frame = (CGRect){destination};
_currentlyTransitioningFrom = indexOfFrom;
_currentlyTransitioningTo = indexOfTo;
if (indexOfFrom != indexOfTo) {
@ -375,7 +354,7 @@ NSInteger kNeverProgressed = -10000;
[weakSelf freeLock];
_currentlyTransitioningFrom = 0;
_currentlyTransitioningTo = 0;
_dummyView.frame = CGRectMake(0.0f, 0.0f, 0.0f, 0.0f);
_dummyView.frame = CGRectZero;
_displayLink.paused = YES;
// Reset the parallel position tracker
}];
@ -462,7 +441,7 @@ NSInteger kNeverProgressed = -10000;
}
/**
* Must be overridden because UIKit destroys the views superview link when used
* Must be overridden because UIKit removes the view's superview when used
* as a navigator - it's considered outside the view hierarchy.
*/
- (UIView *)reactSuperview
@ -490,7 +469,7 @@ NSInteger kNeverProgressed = -10000;
// we can't hook up the VC hierarchy in 'init' because the subviews aren't hooked up yet,
// so we do it on demand here
[self addControllerToClosestParent:_navigationController];
NSInteger viewControllerCount = _navigationController.viewControllers.count;
// The "react count" is the count of views that are visible on the navigation
// stack. There may be more beyond this - that aren't visible, and may be
@ -563,7 +542,6 @@ NSInteger kNeverProgressed = -10000;
_previousRequestedTopOfStack = _requestedTopOfStack;
}
// TODO: This will likely fail when performing multiple pushes/pops. We must
// free the lock only after the *last* push/pop.
- (void)wrapperViewController:(RCTWrapperViewController *)wrapperViewController
@ -574,7 +552,7 @@ didMoveToNavigationController:(UINavigationController *)navigationController
// while a push/pop is in progress.
return;
}
RCTAssert(
(navigationController == nil || [_navigationController.viewControllers containsObject:wrapperViewController]),
@"if navigation controller is not nil, it should container the wrapper view controller"

View File

@ -5,6 +5,8 @@
#import "RCTConvert.h"
#import "RCTNavigator.h"
#import "RCTShadowView.h"
#import "RCTSparseArray.h"
#import "RCTUIManager.h"
@implementation RCTNavigatorManager
@ -24,5 +26,26 @@ RCT_EXPORT_VIEW_PROPERTY(requestedTopOfStack)
};
}
@end
// TODO: remove error callbacks
- (void)requestSchedulingJavaScriptNavigation:(NSNumber *)reactTag
errorCallback:(RCTResponseSenderBlock)errorCallback
callback:(__unused RCTResponseSenderBlock)callback
{
RCT_EXPORT();
[self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
if (reactTag) {
RCTNavigator *navigator = viewRegistry[reactTag];
if ([navigator isKindOfClass:[RCTNavigator class]]) {
BOOL wasAcquired = [navigator requestSchedulingJavaScriptNavigation];
callback(@[@(wasAcquired)]);
} else {
RCTLogError(@"Cannot set lock: %@ (tag #%@) is not an RCTNavigator", navigator, reactTag);
}
} else {
RCTLogError(@"Tag not specified for requestSchedulingJavaScriptNavigation");
}
}];
}
@end

View File

@ -12,6 +12,8 @@
@interface RCTScrollView : RCTView <UIScrollViewDelegate, RCTScrollableProtocol, RCTAutoInsetsProtocol>
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
/**
* If the `contentSize` is not provided, then the `contentSize` will
* automatically be determined by the size of the `RKScrollView` subview.
@ -32,6 +34,4 @@
@property (nonatomic, assign) BOOL centerContent;
@property (nonatomic, copy) NSArray *stickyHeaderIndices;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher;
@end

View File

@ -31,8 +31,7 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
if ((self = [super initWithFrame:frame])) {
[self.panGestureRecognizer addTarget:self action:@selector(handleCustomPan:)];
}
return self;
@ -257,11 +256,6 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
@synthesize nativeMainScrollDelegate = _nativeMainScrollDelegate;
- (instancetype)initWithFrame:(CGRect)frame
{
RCT_NOT_DESIGNATED_INITIALIZER();
}
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super initWithFrame:CGRectZero])) {

View File

@ -31,23 +31,22 @@ RCT_EXPORT_VIEW_PROPERTY(showsHorizontalScrollIndicator)
RCT_EXPORT_VIEW_PROPERTY(showsVerticalScrollIndicator)
RCT_EXPORT_VIEW_PROPERTY(stickyHeaderIndices);
RCT_EXPORT_VIEW_PROPERTY(throttleScrollCallbackMS);
RCT_EXPORT_VIEW_PROPERTY(zoomScale);
RCT_EXPORT_VIEW_PROPERTY(zoomScale); // TODO: this needs to be set first because it resets other props like contentOffset
RCT_EXPORT_VIEW_PROPERTY(contentInset);
RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets);
RCT_EXPORT_VIEW_PROPERTY(contentOffset);
+ (NSDictionary *)constantsToExport
- (NSDictionary *)constantsToExport
{
return
@{
return @{
@"DecelerationRate": @{
@"Normal": @(UIScrollViewDecelerationRateNormal),
@"Fast": @(UIScrollViewDecelerationRateFast),
@"Normal": @(UIScrollViewDecelerationRateNormal),
@"Fast": @(UIScrollViewDecelerationRateFast),
},
@"KeyboardDismissMode": @{
@"None": @(UIScrollViewKeyboardDismissModeNone),
@"Interactive": @(UIScrollViewKeyboardDismissModeInteractive),
@"OnDrag": @(UIScrollViewKeyboardDismissModeOnDrag),
@"None": @(UIScrollViewKeyboardDismissModeNone),
@"Interactive": @(UIScrollViewKeyboardDismissModeInteractive),
@"OnDrag": @(UIScrollViewKeyboardDismissModeOnDrag),
},
};
}

View File

@ -3,29 +3,15 @@
#import <UIKit/UIKit.h>
#import "Layout.h"
#import "RCTUIManager.h"
#import "RCTViewNodeProtocol.h"
@class RCTSparseArray;
// TODO: amalgamate these enums?
typedef NS_ENUM(NSUInteger, RCTLayoutLifecycle) {
RCTLayoutLifecycleUninitialized = 0,
RCTLayoutLifecycleComputed,
RCTLayoutLifecycleDirtied,
};
// TODO: is this still needed?
typedef NS_ENUM(NSUInteger, RCTPropagationLifecycle) {
RCTPropagationLifecycleUninitialized = 0,
RCTPropagationLifecycleComputed,
RCTPropagationLifecycleDirtied,
};
// TODO: move this to text node?
typedef NS_ENUM(NSUInteger, RCTTextLifecycle) {
RCTTextLifecycleUninitialized = 0,
RCTTextLifecycleComputed,
RCTTextLifecycleDirtied,
typedef NS_ENUM(NSUInteger, RCTUpdateLifecycle) {
RCTUpdateLifecycleUninitialized = 0,
RCTUpdateLifecycleComputed,
RCTUpdateLifecycleDirtied,
};
// TODO: is this redundact now?
@ -48,7 +34,7 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *);
@property (nonatomic, copy) NSString *moduleName;
@property (nonatomic, assign) BOOL isBGColorExplicitlySet; // Used to propogate to children
@property (nonatomic, strong) UIColor *backgroundColor; // Used to propogate to children
@property (nonatomic, assign) RCTLayoutLifecycle layoutLifecycle;
@property (nonatomic, assign) RCTUpdateLifecycle layoutLifecycle;
/**
* isNewView - Used to track the first time the view is introduced into the hierarchy. It is initialized YES, then is
@ -122,11 +108,23 @@ typedef void (^RCTApplierBlock)(RCTSparseArray *);
@property (nonatomic, assign) css_wrap_type_t flexWrap;
@property (nonatomic, assign) CGFloat flex;
- (void)collectUpdatedProperties:(NSMutableSet *)viewsWithNewProperties parentProperties:(NSDictionary *)parentProperties;
- (void)collectRootUpdatedFrames:(NSMutableSet *)viewsWithNewFrame parentConstraint:(CGSize)parentConstraint;
- (void)fillCSSNode:(css_node_t *)node;
/**
* Calculate property changes that need to be propagated to the view.
* The applierBlocks set contains RCTApplierBlock functions that must be applied
* on the main thread in order to update the view.
*/
- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties;
// The following are implementation details exposed to subclasses. Do not call them directly
/**
* Calculate all views whose frame needs updating after layout has been calculated.
* The viewsWithNewFrame set contains the reactTags of the views that need updating.
*/
- (void)collectRootUpdatedFrames:(NSMutableSet *)viewsWithNewFrame parentConstraint:(CGSize)parentConstraint;
/**
* The following are implementation details exposed to subclasses. Do not call them directly
*/
- (void)fillCSSNode:(css_node_t *)node;
- (void)dirtyLayout;
- (BOOL)isLayoutDirty;

View File

@ -10,46 +10,6 @@
typedef void (^RCTActionBlock)(RCTShadowView *shadowViewSelf, id value);
typedef void (^RCTResetActionBlock)(RCTShadowView *shadowViewSelf);
@interface RCTLayoutAction : NSObject
@property (nonatomic, readwrite, copy) RCTActionBlock block;
@property (nonatomic, readwrite, copy) RCTResetActionBlock resetBlock;
@property (nonatomic, readwrite, assign) NSInteger precedence;
@end
@implementation RCTLayoutAction @end
#define ACTION_FOR_KEY_DEFAULT(name, default, blockIn) \
do { \
RCTLayoutAction *action = [[RCTLayoutAction alloc] init]; \
action.block = blockIn; \
action.resetBlock = ^(id idSelf1) { \
blockIn(idSelf1, default); \
}; \
actions[@"" #name ""] = action; \
} while(0)
#define ACTION_FOR_KEY(name, blockIn) \
ACTION_FOR_KEY_DEFAULT(name, @([defaultShadowView name]), (blockIn))
#define ACTION_FOR_FLOAT_KEY_DEFAULT(name, default, blockIn) \
ACTION_FOR_KEY_DEFAULT(name, @(default), ^(id idSelf2, NSNumber *n) { \
if (isnan([n floatValue])) { \
RCTLogWarn(@"Got NaN for `"#name"` prop, ignoring"); \
return; \
} \
blockIn(idSelf2, RCTNumberToFloat(n)); \
});
#define ACTION_FOR_FLOAT_KEY(name, blockIn) \
ACTION_FOR_FLOAT_KEY_DEFAULT(name, [defaultShadowView name], (blockIn))
#define ACTION_FOR_DEFAULT_UNDEFINED_KEY(name, blockIn) \
ACTION_FOR_KEY_DEFAULT(name, nil, ^(id idSelf2, NSNumber *n) { \
blockIn(idSelf2, n == nil ? CSS_UNDEFINED : [n floatValue]); \
});
#define MAX_TREE_DEPTH 30
const NSString *const RCTBackgroundColorProp = @"backgroundColor";
@ -65,22 +25,16 @@ typedef enum {
META_PROP_COUNT,
} meta_prop_t;
@interface RCTShadowView()
{
float _paddingMetaProps[META_PROP_COUNT];
float _marginMetaProps[META_PROP_COUNT];
}
@end
@implementation RCTShadowView
{
RCTPropagationLifecycle _propagationLifecycle;
RCTTextLifecycle _textLifecycle;
RCTUpdateLifecycle _propagationLifecycle;
RCTUpdateLifecycle _textLifecycle;
NSDictionary *_lastParentProperties;
NSMutableArray *_reactSubviews;
BOOL _recomputePadding;
BOOL _recomputeMargin;
float _paddingMetaProps[META_PROP_COUNT];
float _marginMetaProps[META_PROP_COUNT];
}
@synthesize reactTag = _reactTag;
@ -131,11 +85,6 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
node->children_count = (int)_reactSubviews.count;
}
- (void)applyLayoutNode:(css_node_t *)node viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame
{
[self _applyLayoutNode:node viewsWithNewFrame:viewsWithNewFrame absolutePosition:CGPointZero];
}
// The absolute stuff is so that we can take into account our absolute position when rounding in order to
// snap to the pixel grid. For example, say you have the following structure:
//
@ -165,24 +114,24 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
// width = 213.5 - 106.5 = 107
// You'll notice that this is the same width we calculated for the parent view because we've taken its position into account.
- (void)_applyLayoutNode:(css_node_t *)node viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame absolutePosition:(CGPoint)absolutePosition
- (void)applyLayoutNode:(css_node_t *)node viewsWithNewFrame:(NSMutableSet *)viewsWithNewFrame absolutePosition:(CGPoint)absolutePosition
{
if (!node->layout.should_update) {
return;
}
node->layout.should_update = false;
_layoutLifecycle = RCTLayoutLifecycleComputed;
_layoutLifecycle = RCTUpdateLifecycleComputed;
CGPoint absoluteTopLeft = {
RCTRoundPixelValue(absolutePosition.x + node->layout.position[CSS_LEFT]),
RCTRoundPixelValue(absolutePosition.y + node->layout.position[CSS_TOP])
};
CGPoint absoluteBottomRight = {
RCTRoundPixelValue(absolutePosition.x + node->layout.position[CSS_LEFT] + node->layout.dimensions[CSS_WIDTH]),
RCTRoundPixelValue(absolutePosition.y + node->layout.position[CSS_TOP] + node->layout.dimensions[CSS_HEIGHT])
};
CGRect frame = {
RCTRoundPixelValue(node->layout.position[CSS_LEFT]),
RCTRoundPixelValue(node->layout.position[CSS_TOP]),
@ -205,7 +154,7 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
for (int i = 0; i < node->children_count; ++i) {
RCTShadowView *child = (RCTShadowView *)_reactSubviews[i];
[child _applyLayoutNode:node->get_child(node->context, i) viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition];
[child applyLayoutNode:node->get_child(node->context, i) viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition];
}
}
@ -238,10 +187,10 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
- (void)collectUpdatedProperties:(NSMutableSet *)applierBlocks parentProperties:(NSDictionary *)parentProperties
{
if (_propagationLifecycle == RCTPropagationLifecycleComputed && [parentProperties isEqualToDictionary:_lastParentProperties]) {
if (_propagationLifecycle == RCTUpdateLifecycleComputed && [parentProperties isEqualToDictionary:_lastParentProperties]) {
return;
}
_propagationLifecycle = RCTPropagationLifecycleComputed;
_propagationLifecycle = RCTUpdateLifecycleComputed;
_lastParentProperties = parentProperties;
NSDictionary *nextProps = [self processBackgroundColor:applierBlocks parentProperties:parentProperties];
for (RCTShadowView *child in _reactSubviews) {
@ -253,7 +202,7 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
{
[self fillCSSNode:_cssNode];
layoutNode(_cssNode, CSS_UNDEFINED);
[self applyLayoutNode:_cssNode viewsWithNewFrame:viewsWithNewFrame];
[self applyLayoutNode:_cssNode viewsWithNewFrame:viewsWithNewFrame absolutePosition:CGPointZero];
}
+ (CGRect)measureLayout:(RCTShadowView *)shadowView relativeTo:(RCTShadowView *)ancestor
@ -277,7 +226,7 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
- (instancetype)init
{
if ((self = [super init])) {
_frame = CGRectMake(0, 0, CSS_UNDEFINED, CSS_UNDEFINED);
for (int ii = 0; ii < META_PROP_COUNT; ii++) {
@ -286,9 +235,9 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
}
_newView = YES;
_layoutLifecycle = RCTLayoutLifecycleUninitialized;
_propagationLifecycle = RCTPropagationLifecycleUninitialized;
_textLifecycle = RCTTextLifecycleUninitialized;
_layoutLifecycle = RCTUpdateLifecycleUninitialized;
_propagationLifecycle = RCTUpdateLifecycleUninitialized;
_textLifecycle = RCTUpdateLifecycleUninitialized;
_reactSubviews = [NSMutableArray array];
@ -309,46 +258,46 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
- (void)dirtyLayout
{
if (_layoutLifecycle != RCTLayoutLifecycleDirtied) {
_layoutLifecycle = RCTLayoutLifecycleDirtied;
if (_layoutLifecycle != RCTUpdateLifecycleDirtied) {
_layoutLifecycle = RCTUpdateLifecycleDirtied;
[_superview dirtyLayout];
}
}
- (BOOL)isLayoutDirty
{
return _layoutLifecycle != RCTLayoutLifecycleComputed;
return _layoutLifecycle != RCTUpdateLifecycleComputed;
}
- (void)dirtyPropagation
{
if (_propagationLifecycle != RCTPropagationLifecycleDirtied) {
_propagationLifecycle = RCTPropagationLifecycleDirtied;
if (_propagationLifecycle != RCTUpdateLifecycleDirtied) {
_propagationLifecycle = RCTUpdateLifecycleDirtied;
[_superview dirtyPropagation];
}
}
- (BOOL)isPropagationDirty
{
return _propagationLifecycle != RCTLayoutLifecycleComputed;
return _propagationLifecycle != RCTUpdateLifecycleComputed;
}
- (void)dirtyText
{
if (_textLifecycle != RCTTextLifecycleDirtied) {
_textLifecycle = RCTTextLifecycleDirtied;
if (_textLifecycle != RCTUpdateLifecycleDirtied) {
_textLifecycle = RCTUpdateLifecycleDirtied;
[_superview dirtyText];
}
}
- (BOOL)isTextDirty
{
return _textLifecycle != RCTTextLifecycleComputed;
return _textLifecycle != RCTUpdateLifecycleComputed;
}
- (void)setTextComputed
{
_textLifecycle = RCTTextLifecycleComputed;
_textLifecycle = RCTUpdateLifecycleComputed;
}
- (void)insertReactSubview:(RCTShadowView *)subview atIndex:(NSInteger)atIndex
@ -387,7 +336,6 @@ static void RCTProcessMetaProps(const float metaProps[META_PROP_COUNT], float st
return [shadowView reactTagAtPoint:relativePoint];
}
}
return self.reactTag;
}

View File

@ -16,8 +16,7 @@
RCT_EXPORT_VIEW_PROPERTY(capInsets)
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode)
- (void)set_src:(id)json forView:(RCTStaticImage *)view withDefaultView:(RCTStaticImage *)defaultView
RCT_CUSTOM_VIEW_PROPERTY(src, RCTStaticImage *)
{
if (json) {
if ([[[json description] pathExtension] caseInsensitiveCompare:@"gif"] == NSOrderedSame) {
@ -29,8 +28,7 @@ RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode)
view.image = defaultView.image;
}
}
- (void)set_tintColor:(id)json forView:(RCTStaticImage *)view withDefaultView:(RCTStaticImage *)defaultView
RCT_CUSTOM_VIEW_PROPERTY(tintColor, RCTStaticImage *)
{
if (json) {
view.renderingMode = UIImageRenderingModeAlwaysTemplate;

View File

@ -10,6 +10,6 @@
@property (nonatomic, assign) BOOL autoCorrect;
@property (nonatomic, assign) UIEdgeInsets paddingEdgeInsets; // TODO: contentInset
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@end

View File

@ -14,11 +14,6 @@
BOOL _jsRequestingFirstResponder;
}
- (instancetype)initWithFrame:(CGRect)frame
{
RCT_NOT_DESIGNATED_INITIALIZER();
}
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super initWithFrame:CGRectZero])) {
@ -44,7 +39,7 @@
- (void)removeReactSubview:(UIView *)subview
{
// TODO: this is a bit broken - if the TextView inserts any of
// TODO: this is a bit broken - if the TextField inserts any of
// it's own views below or between React's, the indices won't match
[_reactSubviews removeObject:subview];
[subview removeFromSuperview];
@ -52,7 +47,7 @@
- (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
{
// TODO: this is a bit broken - if the TextView inserts any of
// TODO: this is a bit broken - if the TextField inserts any of
// it's own views below or between React's, the indices won't match
[_reactSubviews insertObject:view atIndex:atIndex];
[super insertSubview:view atIndex:atIndex];

View File

@ -4,6 +4,7 @@
#import "RCTConvert.h"
#import "RCTShadowView.h"
#import "RCTSparseArray.h"
#import "RCTTextField.h"
@implementation RCTTextFieldManager
@ -15,38 +16,34 @@
RCT_EXPORT_VIEW_PROPERTY(caretHidden)
RCT_EXPORT_VIEW_PROPERTY(autoCorrect)
RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType)
RCT_EXPORT_VIEW_PROPERTY(enabled)
RCT_EXPORT_VIEW_PROPERTY(placeholder)
RCT_EXPORT_VIEW_PROPERTY(text)
RCT_EXPORT_VIEW_PROPERTY(font)
RCT_EXPORT_VIEW_PROPERTY(clearButtonMode)
RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType)
RCT_EXPORT_VIEW_PROPERTY(keyboardType)
RCT_REMAP_VIEW_PROPERTY(color, textColor)
- (void)set_fontSize:(id)json
forView:(RCTTextField *)view
withDefaultView:(RCTTextField *)defaultView
RCT_CUSTOM_VIEW_PROPERTY(fontSize, RCTTextField *)
{
view.font = [RCTConvert UIFont:view.font withSize:json ?: @(defaultView.font.pointSize)];
}
- (void)set_FontWeight:(id)json
forView:(RCTTextField *)view
withDefaultView:(RCTTextField *)defaultView
RCT_CUSTOM_VIEW_PROPERTY(fontWeight, RCTTextField *)
{
view.font = [RCTConvert UIFont:view.font withWeight:json]; // TODO
}
- (void)set_fontFamily:(id)json
forView:(RCTTextField *)view
withDefaultView:(RCTTextField *)defaultView
RCT_CUSTOM_VIEW_PROPERTY(fontFamily, RCTTextField *)
{
view.font = [RCTConvert UIFont:view.font withFamily:json ?: defaultView.font.familyName];
}
// TODO: original code set view.paddingEdgeInsets from shadowView.paddingAsInsets
// could it be that this property is calculated asynchrously on shadow thread?
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView
{
NSNumber *reactTag = shadowView.reactTag;
UIEdgeInsets padding = shadowView.paddingAsInsets;
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
((RCTTextField *)viewRegistry[reactTag]).paddingEdgeInsets = padding;
};
}
@end

View File

@ -13,12 +13,9 @@
RCT_EXPORT_VIEW_PROPERTY(activityIndicatorViewStyle)
RCT_EXPORT_VIEW_PROPERTY(color)
- (void)set_animating:(NSNumber *)value
forView:(UIActivityIndicatorView *)view
withDefaultView:(UIActivityIndicatorView *)defaultView
RCT_CUSTOM_VIEW_PROPERTY(animating, UIActivityIndicatorView *)
{
BOOL animating = value ? [value boolValue] : [defaultView isAnimating];
BOOL animating = json ? [json boolValue] : [defaultView isAnimating];
if (animating != [view isAnimating]) {
if (animating) {
[view startAnimating];
@ -28,7 +25,7 @@ RCT_EXPORT_VIEW_PROPERTY(color)
}
}
+ (NSDictionary *)constantsToExport
- (NSDictionary *)constantsToExport
{
return
@{

View File

@ -11,7 +11,6 @@
@interface RCTView : UIView
@property (nonatomic, assign) RCTPointerEvents pointerEvents;
@property (nonatomic, copy) NSString *overrideAccessibilityLabel;
+ (void)autoAdjustInsetsForView:(UIView<RCTAutoInsetsProtocol> *)parentView
withScrollView:(UIScrollView *)scrollView

View File

@ -34,8 +34,8 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
- (NSString *)accessibilityLabel
{
if (self.overrideAccessibilityLabel) {
return self.overrideAccessibilityLabel;
if (super.accessibilityLabel) {
return super.accessibilityLabel;
}
return RCTRecursiveAccessibilityLabel(self);
}
@ -84,7 +84,7 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
UIEdgeInsets baseInset = parentView.contentInset;
CGFloat previousInsetTop = scrollView.contentInset.top;
CGPoint contentOffset = scrollView.contentOffset;
if (parentView.automaticallyAdjustContentInsets) {
UIEdgeInsets autoInset = [self contentInsetsForView:parentView];
baseInset.top += autoInset.top;
@ -94,7 +94,7 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
}
[scrollView setContentInset:baseInset];
[scrollView setScrollIndicatorInsets:baseInset];
if (updateOffset) {
// If we're adjusting the top inset, then let's also adjust the contentOffset so that the view
// elements above the top guide do not cover the content.

View File

@ -2,9 +2,11 @@
#import <UIKit/UIKit.h>
#import "RCTBridgeModule.h"
#import "RCTConvert.h"
#import "RCTLog.h"
@class RCTBridge;
@class RCTEventDispatcher;
@class RCTShadowView;
@class RCTSparseArray;
@ -12,19 +14,22 @@
typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *viewRegistry);
@interface RCTViewManager : NSObject
@interface RCTViewManager : NSObject <RCTBridgeModule>
/**
* Designated initializer for view modules. Override this when subclassing.
* The bridge can be used to access both the RCTUIIManager and the RCTEventDispatcher,
* allowing the manager (or the views that it manages) to manipulate the view
* hierarchy and send events back to the JS context.
*/
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@property (nonatomic, strong) RCTBridge *bridge;
/**
* The event dispatcher is used to send events back to the JavaScript application.
* It can either be used directly by the module, or passed on to instantiated
* view subclasses so that they can handle their own events.
*/
@property (nonatomic, readonly, weak) RCTEventDispatcher *eventDispatcher;
// TODO: remove this, as it can be accessed directly from bridge
@property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher;
/**
* The module name exposed to React JS. If omitted, this will be inferred
@ -88,34 +93,19 @@ typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *v
+ (NSDictionary *)customDirectEventTypes;
/**
* Injects constants into JS. These constants are made accessible via
* NativeModules.moduleName.X. Note that this method is not inherited when you
* subclass a view module, and you should not call [super constantsToExport]
* when overriding it.
* Called to notify manager that layout has finished, in case any calculated
* properties need to be copied over from shadow view to view.
*/
+ (NSDictionary *)constantsToExport;
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView;
/**
* To deprecate, hopefully
* Called after view hierarchy manipulation has finished, and all shadow props
* have been set, but before layout has been performed. Useful for performing
* custo layout logic or tasks that involve walking the view hierarchy.
* To be deprecated, hopefully.
*/
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry;
/**
* Informal protocol for setting view and shadowView properties.
* Implement methods matching these patterns to set any properties that
* require special treatment (e.g. where the type or name cannot be inferred).
*
* - (void)set_<propertyName>:(id)property
* forView:(UIView *)view
* withDefaultView:(UIView *)defaultView;
*
* - (void)set_<propertyName>:(id)property
* forShadowView:(RCTShadowView *)view
* withDefaultView:(RCTShadowView *)defaultView;
*
* For simple cases, use the macros below:
*/
/**
* This handles the simple case, where JS and native property names match
* And the type can be automatically inferred.
@ -131,10 +121,21 @@ RCT_REMAP_VIEW_PROPERTY(name, name)
- (void)set_##name:(id)json forView:(id)view withDefaultView:(id)defaultView { \
if ((json && !RCTSetProperty(view, @#keypath, json)) || \
(!json && !RCTCopyProperty(view, defaultView, @#keypath))) { \
RCTLogMustFix(@"%@ does not have setter for `%s` property", [view class], #name); \
RCTLogError(@"%@ does not have setter for `%s` property", [view class], #name); \
} \
}
/**
* These macros can be used when you need to provide custom logic for setting
* view properties. The macro should be followed by a method body, which can
* refer to "json", "view" and "defaultView" to implement the required logic.
*/
#define RCT_CUSTOM_VIEW_PROPERTY(name, viewType) \
- (void)set_##name:(id)json forView:(viewType)view withDefaultView:(viewType)defaultView
#define RCT_CUSTOM_SHADOW_PROPERTY(name, viewType) \
- (void)set_##name:(id)json forShadowView:(viewType)view withDefaultView:(viewType)defaultView
/**
* These are useful in cases where the module's superclass handles a
* property, but you wish to "unhandle" it, so it will be ignored.

View File

@ -2,32 +2,33 @@
#import "RCTViewManager.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTShadowView.h"
#import "RCTUtils.h"
#import "RCTView.h"
@implementation RCTViewManager
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
@synthesize bridge = _bridge;
- (RCTEventDispatcher *)eventDispatcher
{
if ((self = [super init])) {
_eventDispatcher = eventDispatcher;
}
return self;
return _bridge.eventDispatcher;
}
+ (NSString *)moduleName
{
// Default implementation, works in most cases
NSString *name = NSStringFromClass(self);
if ([name hasPrefix:@"RK"]) {
name = [name stringByReplacingCharactersInRange:(NSRange){0,@"RK".length} withString:@"RCT"];
}
if ([name hasPrefix:@"RCTUI"]) {
name = [name substringFromIndex:@"RCT".length];
}
if ([name hasSuffix:@"Manager"]) {
name = [name substringToIndex:name.length - @"Manager".length];
}
return name;
}
@ -51,7 +52,12 @@
return nil;
}
+ (NSDictionary *)constantsToExport
- (NSDictionary *)constantsToExport
{
return nil;
}
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView
{
return nil;
}
@ -77,28 +83,22 @@ RCT_REMAP_VIEW_PROPERTY(borderColor, layer.borderColor);
RCT_REMAP_VIEW_PROPERTY(borderRadius, layer.cornerRadius)
RCT_REMAP_VIEW_PROPERTY(borderWidth, layer.borderWidth)
RCT_REMAP_VIEW_PROPERTY(transformMatrix, layer.transform)
- (void)set_overflow:(id)json
forView:(UIView *)view
withDefaultView:(UIView *)defaultView
RCT_CUSTOM_VIEW_PROPERTY(overflow, UIView *)
{
view.clipsToBounds = json ? ![RCTConvert css_overflow:json] : defaultView.clipsToBounds;
}
- (void)set_pointerEvents:(id)json
forView:(RCTView *)view
withDefaultView:(RCTView *)defaultView
RCT_CUSTOM_VIEW_PROPERTY(pointerEvents, RCTView *)
{
if ([view respondsToSelector:@selector(setPointerEvents:)]) {
view.pointerEvents = json ? [RCTConvert RCTPointerEvents:json] : defaultView.pointerEvents;
return;
}
if (!json) {
view.userInteractionEnabled = defaultView.userInteractionEnabled;
return;
}
switch ([RCTConvert NSInteger:json]) {
case RCTPointerEventsUnspecified:
// Pointer events "unspecified" acts as if a stylesheet had not specified,
@ -117,54 +117,34 @@ RCT_REMAP_VIEW_PROPERTY(transformMatrix, layer.transform)
// ShadowView properties
- (void)set_backgroundColor:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
RCT_CUSTOM_SHADOW_PROPERTY(backgroundColor, RCTShadowView *)
{
shadowView.backgroundColor = json ? [RCTConvert UIColor:json] : defaultView.backgroundColor;
shadowView.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet;
view.backgroundColor = json ? [RCTConvert UIColor:json] : defaultView.backgroundColor;
view.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet;
}
- (void)set_flexDirection:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
RCT_CUSTOM_SHADOW_PROPERTY(flexDirection, RCTShadowView *)
{
shadowView.flexDirection = json? [RCTConvert css_flex_direction_t:json] : defaultView.flexDirection;
view.flexDirection = json? [RCTConvert css_flex_direction_t:json] : defaultView.flexDirection;
}
- (void)set_flexWrap:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
RCT_CUSTOM_SHADOW_PROPERTY(flexWrap, RCTShadowView *)
{
shadowView.flexWrap = json ? [RCTConvert css_wrap_type_t:json] : defaultView.flexWrap;
view.flexWrap = json ? [RCTConvert css_wrap_type_t:json] : defaultView.flexWrap;
}
- (void)set_justifyContent:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
RCT_CUSTOM_SHADOW_PROPERTY(justifyContent, RCTShadowView *)
{
shadowView.justifyContent = json ? [RCTConvert css_justify_t:json] : defaultView.justifyContent;
view.justifyContent = json ? [RCTConvert css_justify_t:json] : defaultView.justifyContent;
}
- (void)set_alignItems:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
RCT_CUSTOM_SHADOW_PROPERTY(alignItems, RCTShadowView *)
{
shadowView.alignItems = json ? [RCTConvert css_align_t:json] : defaultView.alignItems;
view.alignItems = json ? [RCTConvert css_align_t:json] : defaultView.alignItems;
}
- (void)set_alignSelf:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
RCT_CUSTOM_SHADOW_PROPERTY(alignSelf, RCTShadowView *)
{
shadowView.alignSelf = json ? [RCTConvert css_align_t:json] : defaultView.alignSelf;
view.alignSelf = json ? [RCTConvert css_align_t:json] : defaultView.alignSelf;
}
- (void)set_position:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
RCT_CUSTOM_SHADOW_PROPERTY(position, RCTShadowView *)
{
shadowView.positionType = json ? [RCTConvert css_position_type_t:json] : defaultView.positionType;
view.positionType = json ? [RCTConvert css_position_type_t:json] : defaultView.positionType;
}
@end

View File

@ -15,8 +15,11 @@ didMoveToNavigationController:(UINavigationController *)navigationController;
@interface RCTWrapperViewController : UIViewController
- (instancetype)initWithContentView:(UIView *)contentView eventDispatcher:(RCTEventDispatcher *)eventDispatcher;
- (instancetype)initWithNavItem:(RCTNavItem *)navItem eventDispatcher:(RCTEventDispatcher *)eventDispatcher;
- (instancetype)initWithContentView:(UIView *)contentView
eventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithNavItem:(RCTNavItem *)navItem
eventDispatcher:(RCTEventDispatcher *)eventDispatcher;
@property (nonatomic, readwrite, weak) id<RCTWrapperViewControllerNavigationListener> navigationListener;
@property (nonatomic, strong, readwrite) RCTNavItem *navItem;

View File

@ -15,11 +15,6 @@
CGFloat _previousBottomLayout;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
RCT_NOT_DESIGNATED_INITIALIZER();
}
- (instancetype)initWithContentView:(UIView *)contentView eventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super initWithNibName:nil bundle:nil])) {