2015-03-23 20:28:42 +00:00
/ * *
* Copyright ( c ) 2015 - present , Facebook , Inc .
* All rights reserved .
*
* This source code is licensed under the BSD - style license found in the
* LICENSE file in the root directory of this source tree . An additional grant
* of patent rights can be found in the PATENTS file in the same directory .
* /
2015-02-20 04:10:52 +00:00
# import "RCTUIManager.h"
# import < objc / message . h >
2015-03-10 21:23:03 +00:00
# import < AVFoundation / AVFoundation . h >
2015-02-20 04:10:52 +00:00
# import "Layout.h"
# import "RCTAnimationType.h"
# import "RCTAssert.h"
# import "RCTBridge.h"
# import "RCTConvert.h"
# import "RCTLog.h"
2015-03-10 21:23:03 +00:00
# import "RCTRootView.h"
2015-02-20 04:10:52 +00:00
# import "RCTScrollableProtocol.h"
# import "RCTShadowView.h"
# import "RCTSparseArray.h"
# import "RCTUtils.h"
# import "RCTView.h"
# import "RCTViewManager.h"
2015-03-10 21:23:03 +00:00
# import "RCTViewNodeProtocol.h"
2015-03-26 09:58:06 +00:00
# import "UIView+React.h"
2015-02-20 04:10:52 +00:00
typedef void ( ^ react_view _node _block _t ) ( id < RCTViewNodeProtocol > ) ;
static void RCTTraverseViewNodes ( id < RCTViewNodeProtocol > view , react_view _node _block _t block )
{
if ( view . reactTag ) block ( view ) ;
for ( id < RCTViewNodeProtocol > subview in view . reactSubviews ) {
RCTTraverseViewNodes ( subview , block ) ;
}
}
@ interface RCTAnimation : NSObject
@ property ( nonatomic , readonly ) NSTimeInterval duration ;
@ property ( nonatomic , readonly ) NSTimeInterval delay ;
@ property ( nonatomic , readonly , copy ) NSString * property ;
@ property ( nonatomic , readonly ) id fromValue ;
@ property ( nonatomic , readonly ) id toValue ;
@ property ( nonatomic , readonly ) CGFloat springDamping ;
@ property ( nonatomic , readonly ) CGFloat initialVelocity ;
@ property ( nonatomic , readonly ) RCTAnimationType animationType ;
@ end
@ implementation RCTAnimation
2015-03-25 00:37:03 +00:00
static UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType ( RCTAnimationType type )
2015-02-20 04:10:52 +00:00
{
switch ( type ) {
case RCTAnimationTypeLinear :
return UIViewAnimationCurveLinear ;
case RCTAnimationTypeEaseIn :
return UIViewAnimationCurveEaseIn ;
case RCTAnimationTypeEaseOut :
return UIViewAnimationCurveEaseOut ;
case RCTAnimationTypeEaseInEaseOut :
return UIViewAnimationCurveEaseInOut ;
default :
2015-03-25 00:37:03 +00:00
RCTLogError ( @ "Unsupported animation type %zd" , type ) ;
2015-02-20 04:10:52 +00:00
return UIViewAnimationCurveEaseInOut ;
}
}
- ( instancetype ) initWithDuration : ( NSTimeInterval ) duration dictionary : ( NSDictionary * ) config
{
if ( ! config ) {
return nil ;
}
if ( ( self = [ super init ] ) ) {
_property = [ RCTConvert NSString : config [ @ "property" ] ] ;
2015-03-30 14:12:38 +00:00
_duration = [ RCTConvert NSTimeInterval : config [ @ "duration" ] ] ? : duration ;
if ( _duration > 0.0 && _duration < 0.01 ) {
RCTLogError ( @ "RCTLayoutAnimation expects timings to be in ms, not seconds." ) ;
_duration = _duration * 1000.0 ;
}
_delay = [ RCTConvert NSTimeInterval : config [ @ "delay" ] ] ;
if ( _delay > 0.0 && _delay < 0.01 ) {
RCTLogError ( @ "RCTLayoutAnimation expects timings to be in ms, not seconds." ) ;
_delay = _delay * 1000.0 ;
}
2015-02-20 04:10:52 +00:00
_animationType = [ RCTConvert RCTAnimationType : config [ @ "type" ] ] ;
if ( _animationType = = RCTAnimationTypeSpring ) {
_springDamping = [ RCTConvert CGFloat : config [ @ "springDamping" ] ] ;
_initialVelocity = [ RCTConvert CGFloat : config [ @ "initialVelocity" ] ] ;
}
_fromValue = config [ @ "fromValue" ] ;
_toValue = config [ @ "toValue" ] ;
}
return self ;
}
- ( void ) performAnimations : ( void ( ^ ) ( void ) ) animations
withCompletionBlock : ( void ( ^ ) ( BOOL completed ) ) completionBlock
{
if ( _animationType = = RCTAnimationTypeSpring ) {
[ UIView animateWithDuration : _duration
delay : _delay
usingSpringWithDamping : _springDamping
initialSpringVelocity : _initialVelocity
options : UIViewAnimationOptionBeginFromCurrentState
animations : animations
completion : completionBlock ] ;
} else {
UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState |
UIViewAnimationCurveFromRCTAnimationType ( _animationType ) ;
[ UIView animateWithDuration : _duration
delay : _delay
options : options
animations : animations
completion : completionBlock ] ;
}
}
@ end
@ interface RCTLayoutAnimation : NSObject
@ property ( nonatomic , strong ) RCTAnimation * createAnimation ;
@ property ( nonatomic , strong ) RCTAnimation * updateAnimation ;
@ property ( nonatomic , strong ) RCTAnimation * deleteAnimation ;
@ property ( nonatomic , strong ) RCTResponseSenderBlock callback ;
@ end
@ implementation RCTLayoutAnimation
- ( instancetype ) initWithDictionary : ( NSDictionary * ) config callback : ( RCTResponseSenderBlock ) callback
{
if ( ! config ) {
return nil ;
}
if ( ( self = [ super init ] ) ) {
2015-03-30 14:12:38 +00:00
NSTimeInterval duration = [ RCTConvert NSTimeInterval : config [ @ "duration" ] ] ;
if ( duration > 0.0 && duration < 0.01 ) {
RCTLogError ( @ "RCTLayoutAnimation expects timings to be in ms, not seconds." ) ;
duration = duration * 1000.0 ;
}
2015-02-20 04:10:52 +00:00
_createAnimation = [ [ RCTAnimation alloc ] initWithDuration : duration dictionary : config [ @ "create" ] ] ;
_updateAnimation = [ [ RCTAnimation alloc ] initWithDuration : duration dictionary : config [ @ "update" ] ] ;
_deleteAnimation = [ [ RCTAnimation alloc ] initWithDuration : duration dictionary : config [ @ "delete" ] ] ;
_callback = callback ;
}
return self ;
}
@ end
2015-03-25 00:37:03 +00:00
@ interface RCTUIManager ( )
// NOTE : these are properties so that they can be accessed by unit tests
@ property ( nonatomic , strong ) RCTSparseArray * viewManagerRegistry ; // RCT thread only
@ property ( nonatomic , strong ) RCTSparseArray * shadowViewRegistry ; // RCT thread only
@ property ( nonatomic , strong ) RCTSparseArray * viewRegistry ; // Main thread only
@ end
2015-02-20 04:10:52 +00:00
@ implementation RCTUIManager
{
2015-03-01 23:33:55 +00:00
dispatch_queue _t _shadowQueue ;
2015-02-20 04:10:52 +00:00
// Root views are only mutated on the shadow queue
NSMutableSet * _rootViewTags ;
NSMutableArray * _pendingUIBlocks ;
NSLock * _pendingUIBlocksLock ;
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
// Animation
RCTLayoutAnimation * _nextLayoutAnimation ; // RCT thread only
RCTLayoutAnimation * _layoutAnimation ; // Main thread only
2015-03-01 23:33:55 +00:00
// Keyed by viewName
2015-02-20 04:10:52 +00:00
NSMutableDictionary * _defaultShadowViews ; // RCT thread only
NSMutableDictionary * _defaultViews ; // Main thread only
NSDictionary * _viewManagers ;
}
2015-03-01 23:33:55 +00:00
@ 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
2015-02-20 04:10:52 +00:00
{
if ( ( self = [ super init ] ) ) {
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
_pendingUIBlocksLock = [ [ NSLock alloc ] init ] ;
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
_defaultShadowViews = [ [ NSMutableDictionary alloc ] init ] ;
_defaultViews = [ [ NSMutableDictionary alloc ] init ] ;
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
_viewManagerRegistry = [ [ RCTSparseArray alloc ] init ] ;
_shadowViewRegistry = [ [ RCTSparseArray alloc ] init ] ;
_viewRegistry = [ [ RCTSparseArray alloc ] init ] ;
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
// Internal resources
_pendingUIBlocks = [ [ NSMutableArray alloc ] init ] ;
_rootViewTags = [ [ NSMutableSet alloc ] init ] ;
}
return self ;
}
2015-03-01 23:33:55 +00:00
- ( void ) dealloc
2015-02-20 04:10:52 +00:00
{
2015-03-01 23:33:55 +00:00
RCTAssert ( ! self . valid , @ "must call -invalidate before -dealloc" ) ;
2015-02-20 04:10:52 +00:00
}
2015-03-01 23:33:55 +00:00
- ( void ) setBridge : ( RCTBridge * ) bridge
2015-02-20 04:10:52 +00:00
{
2015-03-01 23:33:55 +00:00
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 ] ;
}
2015-02-20 04:10:52 +00:00
}
- ( BOOL ) isValid
{
return _viewRegistry ! = nil ;
}
- ( void ) invalidate
{
RCTAssertMainThread ( ) ;
_viewRegistry = nil ;
_shadowViewRegistry = nil ;
2015-03-01 23:33:55 +00:00
_bridge = nil ;
2015-02-20 04:10:52 +00:00
[ _pendingUIBlocksLock lock ] ;
_pendingUIBlocks = nil ;
[ _pendingUIBlocksLock unlock ] ;
}
2015-03-25 00:37:03 +00:00
- ( void ) registerRootView : ( UIView * ) rootView ;
2015-02-20 04:10:52 +00:00
{
RCTAssertMainThread ( ) ;
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
NSNumber * reactTag = rootView . reactTag ;
2015-03-25 00:37:03 +00:00
RCTAssert ( RCTIsReactRootView ( reactTag ) ,
@ "View %@ with tag #%@ is not a root view" , rootView , reactTag ) ;
2015-02-20 04:10:52 +00:00
UIView * existingView = _viewRegistry [ reactTag ] ;
RCTCAssert ( existingView = = nil || existingView = = rootView ,
@ "Expect all root views to have unique tag. Added %@ twice" , reactTag ) ;
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
// Register view
_viewRegistry [ reactTag ] = rootView ;
CGRect frame = rootView . frame ;
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
// Register manager ( TODO : should we do this , or leave it nil ? )
2015-03-25 00:37:03 +00:00
_viewManagerRegistry [ reactTag ] = _viewManagers [ @ "RCTView" ] ;
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
// Register shadow view
2015-03-01 23:33:55 +00:00
dispatch_async ( _shadowQueue , ^ {
2015-02-20 04:10:52 +00:00
RCTShadowView * shadowView = [ [ RCTShadowView alloc ] init ] ;
shadowView . reactTag = reactTag ;
shadowView . frame = frame ;
shadowView . backgroundColor = [ UIColor whiteColor ] ;
_shadowViewRegistry [ shadowView . reactTag ] = shadowView ;
[ _rootViewTags addObject : reactTag ] ;
} ) ;
}
2015-03-25 00:37:03 +00:00
- ( void ) setFrame : ( CGRect ) frame forRootView : ( UIView * ) rootView
{
RCTAssertMainThread ( ) ;
NSNumber * reactTag = rootView . reactTag ;
RCTAssert ( RCTIsReactRootView ( reactTag ) , @ "Specified view %@ is not a root view" , reactTag ) ;
dispatch_async ( _bridge . shadowQueue , ^ {
RCTShadowView * rootShadowView = _shadowViewRegistry [ reactTag ] ;
RCTAssert ( rootShadowView ! = nil , @ "Could not locate root view with tag %@" , reactTag ) ;
rootShadowView . frame = frame ;
[ rootShadowView updateLayout ] ;
RCTViewManagerUIBlock uiBlock = [ self uiBlockWithLayoutUpdateForRootView : rootShadowView ] ;
[ self addUIBlock : uiBlock ] ;
[ self flushUIBlocks ] ;
} ) ;
}
2015-02-20 04:10:52 +00:00
/ * *
* Unregisters views from registries
* /
- ( void ) _purgeChildren : ( NSArray * ) children fromRegistry : ( RCTSparseArray * ) registry
{
for ( id < RCTViewNodeProtocol > child in children ) {
RCTTraverseViewNodes ( registry [ child . reactTag ] , ^ ( id < RCTViewNodeProtocol > subview ) {
RCTAssert ( ! [ subview isReactRootView ] , @ "Root views should not be unregistered" ) ;
if ( [ subview conformsToProtocol : @ protocol ( RCTInvalidating ) ] ) {
[ ( id < RCTInvalidating > ) subview invalidate ] ;
}
registry [ subview . reactTag ] = nil ;
} ) ;
}
}
- ( void ) addUIBlock : ( RCTViewManagerUIBlock ) block
{
RCTAssert ( ! [ NSThread isMainThread ] , @ "This method should only be called on the shadow thread" ) ;
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
__weak RCTUIManager * weakViewManager = self ;
__weak RCTSparseArray * weakViewRegistry = _viewRegistry ;
dispatch_block _t outerBlock = ^ {
RCTUIManager * strongViewManager = weakViewManager ;
RCTSparseArray * strongViewRegistry = weakViewRegistry ;
if ( strongViewManager && strongViewRegistry ) {
block ( strongViewManager , strongViewRegistry ) ;
}
} ;
2015-03-10 16:33:45 +00:00
[ _pendingUIBlocksLock lock ] ;
2015-02-20 04:10:52 +00:00
[ _pendingUIBlocks addObject : outerBlock ] ;
[ _pendingUIBlocksLock unlock ] ;
}
- ( RCTViewManagerUIBlock ) uiBlockWithLayoutUpdateForRootView : ( RCTShadowView * ) rootShadowView
{
2015-03-01 23:33:55 +00:00
RCTAssert ( ! [ NSThread isMainThread ] , @ "This should never be executed on main thread." ) ;
2015-02-20 04:10:52 +00:00
NSMutableSet * viewsWithNewFrames = [ NSMutableSet setWithCapacity : 1 ] ;
// This is nuanced . In the JS thread , we create a new update buffer
// ` frameTags` / ` frames` that is created / mutated in the JS thread . We access
// 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 .
2015-03-01 23:33:55 +00:00
[ rootShadowView collectRootUpdatedFrames : viewsWithNewFrames
parentConstraint : ( CGSize ) { CSS_UNDEFINED , CSS_UNDEFINED } ] ;
2015-02-20 04:10:52 +00:00
// Parallel arrays
NSMutableArray * frameReactTags = [ NSMutableArray arrayWithCapacity : viewsWithNewFrames . count ] ;
NSMutableArray * frames = [ NSMutableArray arrayWithCapacity : viewsWithNewFrames . count ] ;
NSMutableArray * areNew = [ NSMutableArray arrayWithCapacity : viewsWithNewFrames . count ] ;
NSMutableArray * parentsAreNew = [ NSMutableArray arrayWithCapacity : viewsWithNewFrames . count ] ;
for ( RCTShadowView * shadowView in viewsWithNewFrames ) {
[ frameReactTags addObject : shadowView . reactTag ] ;
[ frames addObject : [ NSValue valueWithCGRect : shadowView . frame ] ] ;
[ areNew addObject : @ ( shadowView . isNewView ) ] ;
[ parentsAreNew addObject : @ ( shadowView . superview . isNewView ) ] ;
}
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
for ( RCTShadowView * shadowView in viewsWithNewFrames ) {
// We have to do this after we build the parentsAreNew array .
shadowView . newView = NO ;
}
2015-03-01 23:33:55 +00:00
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 ] ;
}
2015-02-20 04:10:52 +00:00
// Perform layout ( possibly animated )
NSNumber * rootViewTag = rootShadowView . reactTag ;
return ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
for ( NSUInteger ii = 0 ; ii < frames . count ; ii + + ) {
NSNumber * reactTag = frameReactTags [ ii ] ;
UIView * view = viewRegistry [ reactTag ] ;
CGRect frame = [ frames [ ii ] CGRectValue ] ;
void ( ^ completion ) ( BOOL finished ) = ^ ( BOOL finished ) {
if ( self -> _layoutAnimation . callback ) {
self -> _layoutAnimation . callback ( @ [ @ ( finished ) ] ) ;
}
} ;
// Animate view update
BOOL isNew = [ areNew [ ii ] boolValue ] ;
RCTAnimation * updateAnimation = isNew ? nil : _layoutAnimation . updateAnimation ;
if ( updateAnimation ) {
[ updateAnimation performAnimations : ^ {
2015-03-25 00:37:03 +00:00
[ view reactSetFrame : frame ] ;
2015-03-01 23:33:55 +00:00
for ( RCTViewManagerUIBlock block in updateBlocks ) {
block ( self , _viewRegistry ) ;
}
2015-02-20 04:10:52 +00:00
} withCompletionBlock : completion ] ;
} else {
2015-03-25 00:37:03 +00:00
[ view reactSetFrame : frame ] ;
2015-03-01 23:33:55 +00:00
for ( RCTViewManagerUIBlock block in updateBlocks ) {
block ( self , _viewRegistry ) ;
}
2015-02-20 04:10:52 +00:00
completion ( YES ) ;
}
2015-03-01 23:33:55 +00:00
// Animate view creation
2015-02-20 04:10:52 +00:00
BOOL shouldAnimateCreation = isNew && ! [ parentsAreNew [ ii ] boolValue ] ;
RCTAnimation * createAnimation = _layoutAnimation . createAnimation ;
if ( shouldAnimateCreation && createAnimation ) {
if ( [ createAnimation . property isEqualToString : @ "scaleXY" ] ) {
view . layer . transform = CATransform3DMakeScale ( 0 , 0 , 0 ) ;
} else if ( [ createAnimation . property isEqualToString : @ "opacity" ] ) {
view . layer . opacity = 0.0 ;
}
[ createAnimation performAnimations : ^ {
if ( [ createAnimation . property isEqual : @ "scaleXY" ] ) {
view . layer . transform = CATransform3DIdentity ;
} else if ( [ createAnimation . property isEqual : @ "opacity" ] ) {
view . layer . opacity = 1.0 ;
} else {
2015-03-01 23:33:55 +00:00
RCTLogError ( @ "Unsupported layout animation createConfig property %@" ,
createAnimation . property ) ;
}
for ( RCTViewManagerUIBlock block in updateBlocks ) {
block ( self , _viewRegistry ) ;
2015-02-20 04:10:52 +00:00
}
} withCompletionBlock : nil ] ;
}
}
2015-03-01 23:33:55 +00:00
/ * *
* 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 .
* /
2015-03-25 00:37:03 +00:00
UIView * rootView = _viewRegistry [ rootViewTag ] ;
2015-02-20 04:10:52 +00:00
RCTTraverseViewNodes ( rootView , ^ ( id < RCTViewNodeProtocol > view ) {
if ( [ view respondsToSelector : @ selector ( reactBridgeDidFinishTransaction ) ] ) {
[ view reactBridgeDidFinishTransaction ] ;
}
} ) ;
} ;
}
- ( void ) _amendPendingUIBlocksWithStylePropagationUpdateForRootView : ( RCTShadowView * ) topView
{
NSMutableSet * applierBlocks = [ NSMutableSet setWithCapacity : 1 ] ;
[ topView collectUpdatedProperties : applierBlocks parentProperties : @ { } ] ;
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
for ( RCTApplierBlock block in applierBlocks ) {
block ( viewRegistry ) ;
}
} ] ;
}
/ * *
* A method to be called from JS , which takes a container ID and then releases
* all subviews for that container upon receipt .
* /
- ( void ) removeSubviewsFromContainerWithID : ( NSNumber * ) containerID
{
RCT_EXPORT ( ) ;
id < RCTViewNodeProtocol > container = _viewRegistry [ containerID ] ;
RCTAssert ( container ! = nil , @ "container view (for ID %@) not found" , containerID ) ;
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
NSUInteger subviewsCount = [ [ container reactSubviews ] count ] ;
NSMutableArray * indices = [ [ NSMutableArray alloc ] initWithCapacity : subviewsCount ] ;
for ( NSInteger childIndex = 0 ; childIndex < subviewsCount ; childIndex + + ) {
[ indices addObject : @ ( childIndex ) ] ;
}
[ self manageChildren : containerID
moveFromIndices : nil
moveToIndices : nil
addChildReactTags : nil
addAtIndices : nil
removeAtIndices : indices ] ;
}
/ * *
* Disassociates children from container . Doesn ' t remove from registries .
* TODO : use [ NSArray getObjects : buffer ] to reuse same fast buffer each time .
*
* @ returns Array of removed items .
* /
- ( NSArray * ) _childrenToRemoveFromContainer : ( id < RCTViewNodeProtocol > ) container
atIndices : ( NSArray * ) atIndices
{
// If there are no indices to move or the container has no subviews don ' t bother
// We support parents with nil subviews so long as they ' re all nil so this allows for this behavior
if ( [ atIndices count ] = = 0 || [ [ container reactSubviews ] count ] = = 0 ) {
return nil ;
}
// Construction of removed children must be done "up front" , before indices are disturbed by removals .
NSMutableArray * removedChildren = [ NSMutableArray arrayWithCapacity : atIndices . count ] ;
RCTCAssert ( container ! = nil , @ "container view (for ID %@) not found" , container ) ;
for ( NSInteger i = 0 ; i < [ atIndices count ] ; i + + ) {
NSInteger index = [ atIndices [ i ] integerValue ] ;
if ( index < [ [ container reactSubviews ] count ] ) {
[ removedChildren addObject : [ container reactSubviews ] [ index ] ] ;
}
}
if ( removedChildren . count ! = atIndices . count ) {
RCTLogMustFix ( @ "removedChildren count (%tu) was not what we expected (%tu)" , removedChildren . count , atIndices . count ) ;
}
return removedChildren ;
}
- ( void ) _removeChildren : ( NSArray * ) children fromContainer : ( id < RCTViewNodeProtocol > ) container
{
for ( id removedChild in children ) {
[ container removeReactSubview : removedChild ] ;
}
}
- ( void ) removeRootView : ( NSNumber * ) rootReactTag
{
RCT_EXPORT ( ) ;
RCTShadowView * rootShadowView = _shadowViewRegistry [ rootReactTag ] ;
RCTAssert ( rootShadowView . superview = = nil , @ "root view cannot have superview (ID %@)" , rootReactTag ) ;
2015-03-25 00:37:03 +00:00
[ self _purgeChildren : rootShadowView . reactSubviews fromRegistry : _shadowViewRegistry ] ;
_shadowViewRegistry [ rootReactTag ] = nil ;
2015-02-20 04:10:52 +00:00
[ _rootViewTags removeObject : rootReactTag ] ;
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
RCTCAssertMainThread ( ) ;
UIView * rootView = viewRegistry [ rootReactTag ] ;
2015-03-25 00:37:03 +00:00
[ uiManager _purgeChildren : rootView . reactSubviews fromRegistry : viewRegistry ] ;
viewRegistry [ rootReactTag ] = nil ;
2015-02-20 04:10:52 +00:00
} ] ;
}
- ( void ) replaceExistingNonRootView : ( NSNumber * ) reactTag withView : ( NSNumber * ) newReactTag
{
RCT_EXPORT ( ) ;
RCTShadowView * shadowView = _shadowViewRegistry [ reactTag ] ;
RCTAssert ( shadowView ! = nil , @ "shadowView (for ID %@) not found" , reactTag ) ;
RCTShadowView * superShadowView = shadowView . superview ;
RCTAssert ( superShadowView ! = nil , @ "shadowView super (of ID %@) not found" , reactTag ) ;
NSUInteger indexOfView = [ superShadowView . reactSubviews indexOfObject : shadowView ] ;
RCTAssert ( indexOfView ! = NSNotFound , @ "View's superview doesn't claim it as subview (id %@)" , reactTag ) ;
NSArray * removeAtIndices = @ [ @ ( indexOfView ) ] ;
NSArray * addTags = @ [ newReactTag ] ;
[ self manageChildren : superShadowView . reactTag
moveFromIndices : nil
moveToIndices : nil
addChildReactTags : addTags
addAtIndices : removeAtIndices
removeAtIndices : removeAtIndices ] ;
}
- ( void ) manageChildren : ( NSNumber * ) containerReactTag
moveFromIndices : ( NSArray * ) moveFromIndices
moveToIndices : ( NSArray * ) moveToIndices
addChildReactTags : ( NSArray * ) addChildReactTags
addAtIndices : ( NSArray * ) addAtIndices
removeAtIndices : ( NSArray * ) removeAtIndices
{
RCT_EXPORT ( ) ;
[ self _manageChildren : containerReactTag
moveFromIndices : moveFromIndices
moveToIndices : moveToIndices
addChildReactTags : addChildReactTags
addAtIndices : addAtIndices
removeAtIndices : removeAtIndices
registry : _shadowViewRegistry ] ;
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
[ uiManager _manageChildren : containerReactTag
moveFromIndices : moveFromIndices
moveToIndices : moveToIndices
addChildReactTags : addChildReactTags
addAtIndices : addAtIndices
removeAtIndices : removeAtIndices
registry : viewRegistry ] ;
} ] ;
}
- ( void ) _manageChildren : ( NSNumber * ) containerReactTag
moveFromIndices : ( NSArray * ) moveFromIndices
moveToIndices : ( NSArray * ) moveToIndices
addChildReactTags : ( NSArray * ) addChildReactTags
addAtIndices : ( NSArray * ) addAtIndices
removeAtIndices : ( NSArray * ) removeAtIndices
registry : ( RCTSparseArray * ) registry
{
id < RCTViewNodeProtocol > container = registry [ containerReactTag ] ;
RCTAssert ( moveFromIndices . count = = moveToIndices . count , @ "moveFromIndices had size %tu, moveToIndices had size %tu" , moveFromIndices . count , moveToIndices . count ) ;
RCTAssert ( addChildReactTags . count = = addAtIndices . count , @ "there should be at least one react child to add" ) ;
// Removes ( both permanent and temporary moves ) are using "before" indices
NSArray * permanentlyRemovedChildren = [ self _childrenToRemoveFromContainer : container atIndices : removeAtIndices ] ;
NSArray * temporarilyRemovedChildren = [ self _childrenToRemoveFromContainer : container atIndices : moveFromIndices ] ;
[ self _removeChildren : permanentlyRemovedChildren fromContainer : container ] ;
[ self _removeChildren : temporarilyRemovedChildren fromContainer : container ] ;
[ self _purgeChildren : permanentlyRemovedChildren fromRegistry : registry ] ;
// TODO ( #5906496 ) : optimize all these loops - constantly calling array . count is not efficient
// Figure out what to insert - merge temporary inserts and adds
NSMutableDictionary * destinationsToChildrenToAdd = [ NSMutableDictionary dictionary ] ;
for ( NSInteger index = 0 ; index < temporarilyRemovedChildren . count ; index + + ) {
destinationsToChildrenToAdd [ moveToIndices [ index ] ] = temporarilyRemovedChildren [ index ] ;
}
for ( NSInteger index = 0 ; index < addAtIndices . count ; index + + ) {
id view = registry [ addChildReactTags [ index ] ] ;
if ( view ) {
destinationsToChildrenToAdd [ addAtIndices [ index ] ] = view ;
}
}
NSArray * sortedIndices = [ [ destinationsToChildrenToAdd allKeys ] sortedArrayUsingSelector : @ selector ( compare : ) ] ;
for ( NSNumber * reactIndex in sortedIndices ) {
[ container insertReactSubview : destinationsToChildrenToAdd [ reactIndex ] atIndex : [ reactIndex integerValue ] ] ;
}
}
static BOOL RCTCallPropertySetter ( SEL setter , id value , id view , id defaultView , RCTViewManager * manager )
{
// TODO : cache respondsToSelector tests
if ( [ manager respondsToSelector : setter ] ) {
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
if ( value = = [ NSNull null ] ) {
value = nil ;
}
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
( ( void ( * ) ( id , SEL , id , id , id ) ) objc_msgSend ) ( manager , setter , value , view , defaultView ) ;
return YES ;
}
return NO ;
}
static void RCTSetViewProps ( NSDictionary * props , UIView * view ,
UIView * defaultView , RCTViewManager * manager )
{
[ props enumerateKeysAndObjectsUsingBlock : ^ ( NSString * key , id obj , BOOL * stop ) {
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
SEL setter = NSSelectorFromString ( [ NSString stringWithFormat : @ "set_%@:forView:withDefaultView:" , key ] ) ;
RCTCallPropertySetter ( setter , obj , view , defaultView , manager ) ;
2015-03-26 04:29:28 +00:00
2015-02-20 04:10:52 +00:00
} ] ;
}
static void RCTSetShadowViewProps ( NSDictionary * props , RCTShadowView * shadowView ,
RCTShadowView * defaultView , RCTViewManager * manager )
{
[ props enumerateKeysAndObjectsUsingBlock : ^ ( NSString * key , id obj , BOOL * stop ) {
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
SEL setter = NSSelectorFromString ( [ NSString stringWithFormat : @ "set_%@:forShadowView:withDefaultView:" , key ] ) ;
2015-03-26 04:29:28 +00:00
RCTCallPropertySetter ( setter , obj , shadowView , defaultView , manager ) ;
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
} ] ;
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
// Update layout
2015-03-25 00:37:03 +00:00
[ shadowView updateLayout ] ;
2015-02-20 04:10:52 +00:00
}
- ( void ) createAndRegisterViewWithReactTag : ( NSNumber * ) reactTag
2015-03-01 23:33:55 +00:00
viewName : ( NSString * ) viewName
2015-02-20 04:10:52 +00:00
props : ( NSDictionary * ) props
{
RCT_EXPORT ( createView ) ;
2015-03-01 23:33:55 +00:00
RCTViewManager * manager = _viewManagers [ viewName ] ;
2015-02-20 04:10:52 +00:00
if ( manager = = nil ) {
2015-03-01 23:33:55 +00:00
RCTLogWarn ( @ "No manager class found for view with module name \" % @ \ "" , viewName ) ;
2015-02-20 04:10:52 +00:00
manager = [ [ RCTViewManager alloc ] init ] ;
}
// Register manager
_viewManagerRegistry [ reactTag ] = manager ;
// Generate default view , used for resetting default props
2015-03-01 23:33:55 +00:00
if ( ! _defaultShadowViews [ viewName ] ) {
_defaultShadowViews [ viewName ] = [ manager shadowView ] ;
2015-02-20 04:10:52 +00:00
}
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
RCTShadowView * shadowView = [ manager shadowView ] ;
2015-03-17 10:51:58 +00:00
shadowView . viewName = viewName ;
2015-02-20 04:10:52 +00:00
shadowView . reactTag = reactTag ;
2015-03-01 23:33:55 +00:00
RCTSetShadowViewProps ( props , shadowView , _defaultShadowViews [ viewName ] , manager ) ;
2015-02-20 04:10:52 +00:00
_shadowViewRegistry [ shadowView . reactTag ] = shadowView ;
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
RCTCAssertMainThread ( ) ;
// Generate default view , used for resetting default props
2015-03-01 23:33:55 +00:00
if ( ! uiManager -> _defaultViews [ viewName ] ) {
2015-02-20 04:10:52 +00:00
// 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 .
2015-03-01 23:33:55 +00:00
uiManager -> _defaultViews [ viewName ] = [ manager view ] ;
2015-02-20 04:10:52 +00:00
}
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
UIView * view = [ manager view ] ;
if ( view ) {
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
// Set required properties
view . reactTag = reactTag ;
view . multipleTouchEnabled = YES ;
view . userInteractionEnabled = YES ; // required for touch handling
view . layer . allowsGroupOpacity = YES ; // required for touch handling
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
// Set custom properties
2015-03-01 23:33:55 +00:00
RCTSetViewProps ( props , view , uiManager -> _defaultViews [ viewName ] , manager ) ;
2015-02-20 04:10:52 +00:00
}
viewRegistry [ view . reactTag ] = view ;
} ] ;
}
2015-03-01 23:33:55 +00:00
// TODO : remove viewName param as it isn ' t needed
- ( void ) updateView : ( NSNumber * ) reactTag viewName : ( __unused NSString * ) _ props : ( NSDictionary * ) props
2015-02-20 04:10:52 +00:00
{
RCT_EXPORT ( ) ;
RCTViewManager * viewManager = _viewManagerRegistry [ reactTag ] ;
2015-03-01 23:33:55 +00:00
NSString * viewName = RCTViewNameForModuleName ( [ [ viewManager class ] moduleName ] ) ;
2015-02-20 04:10:52 +00:00
RCTShadowView * shadowView = _shadowViewRegistry [ reactTag ] ;
2015-03-01 23:33:55 +00:00
RCTSetShadowViewProps ( props , shadowView , _defaultShadowViews [ viewName ] , viewManager ) ;
2015-02-20 04:10:52 +00:00
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
UIView * view = uiManager -> _viewRegistry [ reactTag ] ;
2015-03-01 23:33:55 +00:00
RCTSetViewProps ( props , view , uiManager -> _defaultViews [ viewName ] , viewManager ) ;
2015-02-20 04:10:52 +00:00
} ] ;
}
- ( void ) becomeResponder : ( NSNumber * ) reactTag
{
RCT_EXPORT ( focus ) ;
if ( ! reactTag ) return ;
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
UIView * newResponder = viewRegistry [ reactTag ] ;
2015-03-25 00:37:03 +00:00
[ newResponder reactWillMakeFirstResponder ] ;
2015-02-20 04:10:52 +00:00
[ newResponder becomeFirstResponder ] ;
2015-03-25 00:37:03 +00:00
[ newResponder reactDidMakeFirstResponder ] ;
2015-02-20 04:10:52 +00:00
} ] ;
}
- ( void ) resignResponder : ( NSNumber * ) reactTag
{
RCT_EXPORT ( blur ) ;
if ( ! reactTag ) return ;
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
UIView * currentResponder = viewRegistry [ reactTag ] ;
[ currentResponder resignFirstResponder ] ;
} ] ;
}
- ( void ) batchDidComplete
{
2015-03-01 23:33:55 +00:00
// 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 )
2015-02-20 04:10:52 +00:00
for ( RCTViewManager * manager in _viewManagers . allValues ) {
RCTViewManagerUIBlock uiBlock = [ manager uiBlockToAmendWithShadowViewRegistry : _shadowViewRegistry ] ;
if ( uiBlock ) {
[ self addUIBlock : uiBlock ] ;
}
}
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
// Set up next layout animation
if ( _nextLayoutAnimation ) {
RCTLayoutAnimation * layoutAnimation = _nextLayoutAnimation ;
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
uiManager -> _layoutAnimation = layoutAnimation ;
} ] ;
}
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
// Perform layout
for ( NSNumber * reactTag in _rootViewTags ) {
RCTShadowView * rootView = _shadowViewRegistry [ reactTag ] ;
[ self addUIBlock : [ self uiBlockWithLayoutUpdateForRootView : rootView ] ] ;
[ self _amendPendingUIBlocksWithStylePropagationUpdateForRootView : rootView ] ;
}
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
// Clear layout animations
if ( _nextLayoutAnimation ) {
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
uiManager -> _layoutAnimation = nil ;
} ] ;
_nextLayoutAnimation = nil ;
}
2015-03-01 23:33:55 +00:00
2015-03-25 00:37:03 +00:00
[ self flushUIBlocks ] ;
}
- ( void ) flushUIBlocks
{
RCTAssert ( ! [ NSThread isMainThread ] , @ "Should be called on shadow thread" ) ;
2015-03-01 23:33:55 +00:00
// 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 .
2015-02-20 04:10:52 +00:00
[ _pendingUIBlocksLock lock ] ;
NSArray * previousPendingUIBlocks = _pendingUIBlocks ;
_pendingUIBlocks = [ [ NSMutableArray alloc ] init ] ;
[ _pendingUIBlocksLock unlock ] ;
2015-03-01 23:33:55 +00:00
// Execute the previously queued UI blocks
2015-02-20 04:10:52 +00:00
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
for ( dispatch_block _t block in previousPendingUIBlocks ) {
block ( ) ;
}
} ) ;
}
- ( void ) measure : ( NSNumber * ) reactTag callback : ( RCTResponseSenderBlock ) callback
{
RCT_EXPORT ( ) ;
if ( ! callback ) {
RCTLogError ( @ "Called measure with no callback" ) ;
return ;
}
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
UIView * view = viewRegistry [ reactTag ] ;
if ( ! view ) {
2015-03-25 00:37:03 +00:00
RCTLogError ( @ "measure cannot find view with tag %@" , reactTag ) ;
2015-02-20 04:10:52 +00:00
return ;
}
CGRect frame = view . frame ;
UIView * rootView = view ;
while ( rootView && ! [ rootView isReactRootView ] ) {
rootView = rootView . superview ;
}
2015-03-25 00:37:03 +00:00
// TODO : this doesn ' t work because sometimes view is inside a modal window
// RCTCAssert ( [ rootView isReactRootView ] , @ "React view is not inside a react root view" ) ;
2015-03-01 23:33:55 +00:00
2015-02-20 04:10:52 +00:00
// 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 ] ;
callback ( @ [
@ ( frame . origin . x ) ,
@ ( frame . origin . y ) ,
@ ( frame . size . width ) ,
@ ( frame . size . height ) ,
@ ( pagePoint . x ) ,
@ ( pagePoint . y )
] ) ;
} ] ;
}
2015-03-25 00:37:03 +00:00
static void RCTMeasureLayout ( RCTShadowView * view ,
RCTShadowView * ancestor ,
RCTResponseSenderBlock callback )
2015-02-20 04:10:52 +00:00
{
if ( ! view ) {
2015-03-01 23:33:55 +00:00
RCTLogError ( @ "Attempting to measure view that does not exist" ) ;
2015-02-20 04:10:52 +00:00
return ;
}
if ( ! ancestor ) {
2015-03-01 23:33:55 +00:00
RCTLogError ( @ "Attempting to measure relative to ancestor that does not exist" ) ;
2015-02-20 04:10:52 +00:00
return ;
}
2015-03-25 00:37:03 +00:00
CGRect result = [ view measureLayoutRelativeToAncestor : ancestor ] ;
2015-02-20 04:10:52 +00:00
if ( CGRectIsNull ( result ) ) {
2015-03-01 23:33:55 +00:00
RCTLogError ( @ "view %@ (tag #%@) is not a decendant of %@ (tag #%@)" ,
view , view . reactTag , ancestor , ancestor . reactTag ) ;
2015-02-20 04:10:52 +00:00
return ;
}
CGFloat leftOffset = result . origin . x ;
CGFloat topOffset = result . origin . y ;
CGFloat width = result . size . width ;
CGFloat height = result . size . height ;
if ( isnan ( leftOffset ) || isnan ( topOffset ) || isnan ( width ) || isnan ( height ) ) {
2015-03-01 23:33:55 +00:00
RCTLogError ( @ "Attempted to measure layout but offset or dimensions were NaN" ) ;
2015-02-20 04:10:52 +00:00
return ;
}
2015-03-25 00:37:03 +00:00
callback ( @ [ @ ( leftOffset ) , @ ( topOffset ) , @ ( width ) , @ ( height ) ] ) ;
2015-02-20 04:10:52 +00:00
}
/ * *
* Returns the computed recursive offset layout in a dictionary form . The
* returned values are relative to the ` ancestor` shadow view . Returns ` nil` , if
* the ` ancestor` shadow view is not actually an ` ancestor` . Does not touch
* anything on the main UI thread . Invokes supplied callback with ( x , y , width ,
* height ) .
* /
- ( void ) measureLayout : ( NSNumber * ) reactTag
relativeTo : ( NSNumber * ) ancestorReactTag
errorCallback : ( RCTResponseSenderBlock ) errorCallback
callback : ( RCTResponseSenderBlock ) callback
{
RCT_EXPORT ( ) ;
RCTShadowView * shadowView = _shadowViewRegistry [ reactTag ] ;
RCTShadowView * ancestorShadowView = _shadowViewRegistry [ ancestorReactTag ] ;
2015-03-25 00:37:03 +00:00
RCTMeasureLayout ( shadowView , ancestorShadowView , callback ) ;
2015-02-20 04:10:52 +00:00
}
/ * *
* Returns the computed recursive offset layout in a dictionary form . The
* returned values are relative to the ` ancestor` shadow view . Returns ` nil` , if
* the ` ancestor` shadow view is not actually an ` ancestor` . Does not touch
* anything on the main UI thread . Invokes supplied callback with ( x , y , width ,
* height ) .
* /
- ( void ) measureLayoutRelativeToParent : ( NSNumber * ) reactTag
errorCallback : ( RCTResponseSenderBlock ) errorCallback
callback : ( RCTResponseSenderBlock ) callback
{
RCT_EXPORT ( ) ;
RCTShadowView * shadowView = _shadowViewRegistry [ reactTag ] ;
2015-03-25 00:37:03 +00:00
RCTMeasureLayout ( shadowView , shadowView . reactSuperview , callback ) ;
2015-02-20 04:10:52 +00:00
}
/ * *
* Returns an array of computed offset layouts in a dictionary form . The layouts are of any react subviews
* that are immediate descendants to the parent view found within a specified rect . The dictionary result
* contains left , top , width , height and an index . The index specifies the position among the other subviews .
* Only layouts for views that are within the rect passed in are returned . Invokes the error callback if the
* passed in parent view does not exist . Invokes the supplied callback with the array of computed layouts .
* /
- ( void ) measureViewsInRect : ( NSDictionary * ) rect
parentView : ( NSNumber * ) reactTag
errorCallback : ( RCTResponseSenderBlock ) errorCallback
callback : ( RCTResponseSenderBlock ) callback
{
RCT_EXPORT ( ) ;
RCTShadowView * shadowView = _shadowViewRegistry [ reactTag ] ;
if ( ! shadowView ) {
2015-03-01 23:33:55 +00:00
RCTLogError ( @ "Attempting to measure view that does not exist (tag #%@)" , reactTag ) ;
2015-02-20 04:10:52 +00:00
return ;
}
NSArray * childShadowViews = [ shadowView reactSubviews ] ;
NSMutableArray * results = [ [ NSMutableArray alloc ] initWithCapacity : [ childShadowViews count ] ] ;
CGRect layoutRect = [ RCTConvert CGRect : rect ] ;
2015-03-23 20:28:42 +00:00
2015-03-17 10:51:58 +00:00
[ childShadowViews enumerateObjectsUsingBlock : ^ ( RCTShadowView * childShadowView , NSUInteger idx , BOOL * stop ) {
2015-03-25 00:37:03 +00:00
CGRect childLayout = [ childShadowView measureLayoutRelativeToAncestor : shadowView ] ;
2015-02-20 04:10:52 +00:00
if ( CGRectIsNull ( childLayout ) ) {
2015-03-01 23:33:55 +00:00
RCTLogError ( @ "View %@ (tag #%@) is not a decendant of %@ (tag #%@)" ,
childShadowView , childShadowView . reactTag , shadowView , shadowView . reactTag ) ;
2015-02-20 04:10:52 +00:00
return ;
}
CGFloat leftOffset = childLayout . origin . x ;
CGFloat topOffset = childLayout . origin . y ;
CGFloat width = childLayout . size . width ;
CGFloat height = childLayout . size . height ;
if ( leftOffset <= layoutRect . origin . x + layoutRect . size . width &&
leftOffset + width >= layoutRect . origin . x &&
topOffset <= layoutRect . origin . y + layoutRect . size . height &&
topOffset + height >= layoutRect . origin . y ) {
// This view is within the layout rect
2015-03-17 10:51:58 +00:00
NSDictionary * result = @ { @ "index" : @ ( idx ) ,
2015-02-20 04:10:52 +00:00
@ "left" : @ ( leftOffset ) ,
@ "top" : @ ( topOffset ) ,
@ "width" : @ ( width ) ,
@ "height" : @ ( height ) } ;
[ results addObject : result ] ;
}
2015-03-17 10:51:58 +00:00
} ] ;
2015-02-20 04:10:52 +00:00
callback ( @ [ results ] ) ;
}
- ( void ) setMainScrollViewTag : ( NSNumber * ) reactTag
{
RCT_EXPORT ( ) ;
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
// - There should be at most one designated "main scroll view"
// - There should be at most one designated "`nativeMainScrollDelegate`"
// - The one designated main scroll view should have the one designated
// ` nativeMainScrollDelegate` set as its ` nativeMainScrollDelegate` .
if ( uiManager . mainScrollView ) {
uiManager . mainScrollView . nativeMainScrollDelegate = nil ;
}
if ( reactTag ) {
id rkObject = viewRegistry [ reactTag ] ;
if ( [ rkObject conformsToProtocol : @ protocol ( RCTScrollableProtocol ) ] ) {
uiManager . mainScrollView = ( id < RCTScrollableProtocol > ) rkObject ;
( ( id < RCTScrollableProtocol > ) rkObject ) . nativeMainScrollDelegate = uiManager . nativeMainScrollDelegate ;
} else {
RCTCAssert ( NO , @ "Tag %@ does not conform to RCTScrollableProtocol" , reactTag ) ;
}
} else {
uiManager . mainScrollView = nil ;
}
} ] ;
}
- ( void ) scrollToOffsetWithView : ( NSNumber * ) reactTag scrollToOffsetX : ( NSNumber * ) offsetX offsetY : ( NSNumber * ) offsetY
{
RCT_EXPORT ( scrollTo ) ;
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
UIView * view = viewRegistry [ reactTag ] ;
if ( [ view conformsToProtocol : @ protocol ( RCTScrollableProtocol ) ] ) {
[ ( id < RCTScrollableProtocol > ) view scrollToOffset : CGPointMake ( [ offsetX floatValue ] , [ offsetY floatValue ] ) ] ;
} else {
RCTLogError ( @ "tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag %@" , view , reactTag ) ;
}
} ] ;
}
- ( void ) zoomToRectWithView : ( NSNumber * ) reactTag rect : ( NSDictionary * ) rectDict
{
RCT_EXPORT ( zoomToRect ) ;
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
UIView * view = viewRegistry [ reactTag ] ;
if ( [ view conformsToProtocol : @ protocol ( RCTScrollableProtocol ) ] ) {
[ ( id < RCTScrollableProtocol > ) view zoomToRect : [ RCTConvert CGRect : rectDict ] animated : YES ] ;
} else {
RCTLogError ( @ "tried to zoomToRect: on non-RCTScrollableProtocol view %@ with tag %@" , view , reactTag ) ;
}
} ] ;
}
/ * *
* JS sets what * it * considers to be the responder . Later , scroll views can use
* this in order to determine if scrolling is appropriate .
* /
- ( void ) setJSResponder : ( NSNumber * ) reactTag
{
RCT_EXPORT ( ) ;
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
_jsResponder = viewRegistry [ reactTag ] ;
if ( ! _jsResponder ) {
2015-03-01 23:33:55 +00:00
RCTLogError ( @ "Invalid view set to be the JS responder - tag %zd" , reactTag ) ;
2015-02-20 04:10:52 +00:00
}
} ] ;
}
- ( void ) clearJSResponder
{
RCT_EXPORT ( ) ;
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
_jsResponder = nil ;
} ] ;
}
2015-03-01 23:33:55 +00:00
// 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
2015-02-20 04:10:52 +00:00
{
NSMutableDictionary * customBubblingEventTypesConfigs = [ @ {
// Bubble dispatched events
@ "topTap" : @ {
@ "phasedRegistrationNames" : @ {
2015-03-06 00:36:41 +00:00
@ "bubbled" : @ "onPress" ,
@ "captured" : @ "onPressCapture"
2015-02-20 04:10:52 +00:00
}
} ,
@ "topVisibleCellsChange" : @ {
@ "phasedRegistrationNames" : @ {
@ "bubbled" : @ "onVisibleCellsChange" ,
@ "captured" : @ "onVisibleCellsChangeCapture"
}
} ,
@ "topNavigateBack" : @ {
@ "phasedRegistrationNames" : @ {
@ "bubbled" : @ "onNavigationComplete" ,
@ "captured" : @ "onNavigationCompleteCapture"
}
} ,
@ "topNavRightButtonTap" : @ {
@ "phasedRegistrationNames" : @ {
@ "bubbled" : @ "onNavRightButtonTap" ,
@ "captured" : @ "onNavRightButtonTapCapture"
}
} ,
@ "topChange" : @ {
@ "phasedRegistrationNames" : @ {
@ "bubbled" : @ "onChange" ,
@ "captured" : @ "onChangeCapture"
}
} ,
@ "topFocus" : @ {
@ "phasedRegistrationNames" : @ {
@ "bubbled" : @ "onFocus" ,
@ "captured" : @ "onFocusCapture"
}
} ,
@ "topBlur" : @ {
@ "phasedRegistrationNames" : @ {
@ "bubbled" : @ "onBlur" ,
@ "captured" : @ "onBlurCapture"
}
} ,
@ "topSubmitEditing" : @ {
@ "phasedRegistrationNames" : @ {
@ "bubbled" : @ "onSubmitEditing" ,
@ "captured" : @ "onSubmitEditingCapture"
}
} ,
@ "topEndEditing" : @ {
@ "phasedRegistrationNames" : @ {
@ "bubbled" : @ "onEndEditing" ,
@ "captured" : @ "onEndEditingCapture"
}
} ,
@ "topTextInput" : @ {
@ "phasedRegistrationNames" : @ {
@ "bubbled" : @ "onTextInput" ,
@ "captured" : @ "onTextInputCapture"
}
} ,
@ "topTouchStart" : @ {
@ "phasedRegistrationNames" : @ {
@ "bubbled" : @ "onTouchStart" ,
@ "captured" : @ "onTouchStartCapture"
}
} ,
@ "topTouchMove" : @ {
@ "phasedRegistrationNames" : @ {
@ "bubbled" : @ "onTouchMove" ,
@ "captured" : @ "onTouchMoveCapture"
}
} ,
@ "topTouchCancel" : @ {
@ "phasedRegistrationNames" : @ {
@ "bubbled" : @ "onTouchCancel" ,
@ "captured" : @ "onTouchCancelCapture"
}
} ,
@ "topTouchEnd" : @ {
@ "phasedRegistrationNames" : @ {
@ "bubbled" : @ "onTouchEnd" ,
@ "captured" : @ "onTouchEndCapture"
}
} ,
} mutableCopy ] ;
2015-03-25 00:37:03 +00:00
[ _viewManagers enumerateKeysAndObjectsUsingBlock : ^ ( NSString * name , RCTViewManager * manager , BOOL * stop ) {
if ( RCTClassOverridesInstanceMethod ( [ manager class ] , @ selector ( customBubblingEventTypes ) ) ) {
NSDictionary * eventTypes = [ manager customBubblingEventTypes ] ;
2015-02-20 04:10:52 +00:00
for ( NSString * eventName in eventTypes ) {
2015-03-01 23:33:55 +00:00
RCTCAssert ( ! customBubblingEventTypesConfigs [ eventName ] ,
@ "Event '%@' registered multiple times." , eventName ) ;
2015-02-20 04:10:52 +00:00
}
[ customBubblingEventTypesConfigs addEntriesFromDictionary : eventTypes ] ;
}
} ] ;
return customBubblingEventTypesConfigs ;
}
2015-03-01 23:33:55 +00:00
- ( NSDictionary * ) customDirectEventTypes
2015-02-20 04:10:52 +00:00
{
NSMutableDictionary * customDirectEventTypes = [ @ {
@ "topScrollBeginDrag" : @ {
@ "registrationName" : @ "onScrollBeginDrag"
} ,
@ "topScroll" : @ {
@ "registrationName" : @ "onScroll"
} ,
@ "topScrollEndDrag" : @ {
@ "registrationName" : @ "onScrollEndDrag"
} ,
@ "topScrollAnimationEnd" : @ {
@ "registrationName" : @ "onScrollAnimationEnd"
} ,
@ "topSelectionChange" : @ {
@ "registrationName" : @ "onSelectionChange"
} ,
@ "topMomentumScrollBegin" : @ {
@ "registrationName" : @ "onMomentumScrollBegin"
} ,
@ "topMomentumScrollEnd" : @ {
@ "registrationName" : @ "onMomentumScrollEnd"
} ,
@ "topPullToRefresh" : @ {
@ "registrationName" : @ "onPullToRefresh"
} ,
@ "topLoadingStart" : @ {
@ "registrationName" : @ "onLoadingStart"
} ,
@ "topLoadingFinish" : @ {
@ "registrationName" : @ "onLoadingFinish"
} ,
@ "topLoadingError" : @ {
@ "registrationName" : @ "onLoadingError"
} ,
} mutableCopy ] ;
2015-03-25 00:37:03 +00:00
[ _viewManagers enumerateKeysAndObjectsUsingBlock : ^ ( NSString * name , RCTViewManager * manager , BOOL * stop ) {
if ( RCTClassOverridesInstanceMethod ( [ manager class ] , @ selector ( customDirectEventTypes ) ) ) {
NSDictionary * eventTypes = [ manager customDirectEventTypes ] ;
2015-02-20 04:10:52 +00:00
for ( NSString * eventName in eventTypes ) {
RCTCAssert ( ! customDirectEventTypes [ eventName ] , @ "Event '%@' registered multiple times." , eventName ) ;
}
[ customDirectEventTypes addEntriesFromDictionary : eventTypes ] ;
}
} ] ;
return customDirectEventTypes ;
}
2015-03-01 23:33:55 +00:00
- ( NSDictionary * ) constantsToExport
2015-02-20 04:10:52 +00:00
{
NSMutableDictionary * allJSConstants = [ @ {
2015-03-01 23:33:55 +00:00
@ "customBubblingEventTypes" : [ self customBubblingEventTypes ] ,
@ "customDirectEventTypes" : [ self customDirectEventTypes ] ,
2015-02-20 04:10:52 +00:00
@ "NSTextAlignment" : @ {
@ "Left" : @ ( NSTextAlignmentLeft ) ,
@ "Center" : @ ( NSTextAlignmentCenter ) ,
@ "Right" : @ ( NSTextAlignmentRight ) ,
} ,
@ "Dimensions" : @ {
@ "window" : @ {
@ "width" : @ ( RCTScreenSize ( ) . width ) ,
@ "height" : @ ( RCTScreenSize ( ) . height ) ,
@ "scale" : @ ( RCTScreenScale ( ) ) ,
} ,
@ "modalFullscreenView" : @ {
@ "width" : @ ( RCTScreenSize ( ) . width ) ,
2015-03-25 00:37:03 +00:00
@ "height" : @ ( RCTScreenSize ( ) . height ) ,
2015-02-20 04:10:52 +00:00
} ,
} ,
@ "StyleConstants" : @ {
@ "PointerEventsValues" : @ {
@ "none" : @ ( RCTPointerEventsNone ) ,
2015-03-04 22:04:52 +00:00
@ "box-none" : @ ( RCTPointerEventsBoxNone ) ,
@ "box-only" : @ ( RCTPointerEventsBoxOnly ) ,
@ "auto" : @ ( RCTPointerEventsUnspecified ) ,
2015-02-20 04:10:52 +00:00
} ,
} ,
@ "UIText" : @ {
@ "AutocapitalizationType" : @ {
2015-03-04 22:04:52 +00:00
@ "characters" : @ ( UITextAutocapitalizationTypeAllCharacters ) ,
@ "sentences" : @ ( UITextAutocapitalizationTypeSentences ) ,
@ "words" : @ ( UITextAutocapitalizationTypeWords ) ,
@ "none" : @ ( UITextAutocapitalizationTypeNone ) ,
2015-02-20 04:10:52 +00:00
} ,
} ,
2015-02-27 16:40:52 +00:00
@ "UITextField" : @ {
@ "clearButtonMode" : @ {
2015-03-04 22:04:52 +00:00
@ "never" : @ ( UITextFieldViewModeNever ) ,
@ "while-editing" : @ ( UITextFieldViewModeWhileEditing ) ,
@ "unless-editing" : @ ( UITextFieldViewModeUnlessEditing ) ,
@ "always" : @ ( UITextFieldViewModeAlways ) ,
2015-02-27 16:40:52 +00:00
} ,
} ,
2015-03-31 01:25:30 +00:00
@ "UIKeyboardType" : @ {
@ "default" : @ ( UIKeyboardTypeDefault ) ,
@ "ascii-capable" : @ ( UIKeyboardTypeASCIICapable ) ,
@ "numbers-and-punctuation" : @ ( UIKeyboardTypeNumbersAndPunctuation ) ,
@ "url" : @ ( UIKeyboardTypeURL ) ,
@ "number-pad" : @ ( UIKeyboardTypeNumberPad ) ,
@ "phone-pad" : @ ( UIKeyboardTypePhonePad ) ,
@ "name-phone-pad" : @ ( UIKeyboardTypeNamePhonePad ) ,
@ "decimal-pad" : @ ( UIKeyboardTypeDecimalPad ) ,
@ "email-address" : @ ( UIKeyboardTypeEmailAddress ) ,
@ "twitter" : @ ( UIKeyboardTypeTwitter ) ,
@ "web-search" : @ ( UIKeyboardTypeWebSearch ) ,
} ,
@ "UIReturnKeyType" : @ {
@ "default" : @ ( UIReturnKeyDefault ) ,
@ "go" : @ ( UIReturnKeyGo ) ,
@ "google" : @ ( UIReturnKeyGoogle ) ,
@ "join" : @ ( UIReturnKeyJoin ) ,
@ "next" : @ ( UIReturnKeyNext ) ,
@ "route" : @ ( UIReturnKeyRoute ) ,
@ "search" : @ ( UIReturnKeySearch ) ,
@ "send" : @ ( UIReturnKeySend ) ,
@ "yahoo" : @ ( UIReturnKeyYahoo ) ,
@ "done" : @ ( UIReturnKeyDone ) ,
@ "emergency-call" : @ ( UIReturnKeyEmergencyCall ) ,
} ,
2015-02-20 04:10:52 +00:00
@ "UIView" : @ {
@ "ContentMode" : @ {
@ "ScaleToFill" : @ ( UIViewContentModeScaleToFill ) ,
@ "ScaleAspectFit" : @ ( UIViewContentModeScaleAspectFit ) ,
@ "ScaleAspectFill" : @ ( UIViewContentModeScaleAspectFill ) ,
@ "Redraw" : @ ( UIViewContentModeRedraw ) ,
@ "Center" : @ ( UIViewContentModeCenter ) ,
@ "Top" : @ ( UIViewContentModeTop ) ,
@ "Bottom" : @ ( UIViewContentModeBottom ) ,
@ "Left" : @ ( UIViewContentModeLeft ) ,
@ "Right" : @ ( UIViewContentModeRight ) ,
@ "TopLeft" : @ ( UIViewContentModeTopLeft ) ,
@ "TopRight" : @ ( UIViewContentModeTopRight ) ,
@ "BottomLeft" : @ ( UIViewContentModeBottomLeft ) ,
@ "BottomRight" : @ ( UIViewContentModeBottomRight ) ,
} ,
} ,
} mutableCopy ] ;
2015-03-25 00:37:03 +00:00
[ _viewManagers enumerateKeysAndObjectsUsingBlock : ^ ( NSString * name , RCTViewManager * manager , BOOL * stop ) {
2015-02-20 04:10:52 +00:00
// TODO : should these be inherited ?
2015-03-25 00:37:03 +00:00
NSDictionary * constants = RCTClassOverridesInstanceMethod ( [ manager class ] , @ selector ( constantsToExport ) ) ? [ manager constantsToExport ] : nil ;
if ( constants . count ) {
2015-03-01 23:33:55 +00:00
NSMutableDictionary * constantsNamespace = [ NSMutableDictionary dictionaryWithDictionary : allJSConstants [ name ] ] ;
RCTAssert ( constantsNamespace [ @ "Constants" ] = = nil , @ "Cannot redefine Constants in namespace: %@" , name ) ;
2015-02-20 04:10:52 +00:00
// add an additional ' Constants ' namespace for each class
2015-03-01 23:33:55 +00:00
constantsNamespace [ @ "Constants" ] = constants ;
allJSConstants [ name ] = [ constantsNamespace copy ] ;
2015-02-20 04:10:52 +00:00
}
} ] ;
return allJSConstants ;
}
- ( void ) configureNextLayoutAnimation : ( NSDictionary * ) config
withCallback : ( RCTResponseSenderBlock ) callback
errorCallback : ( RCTResponseSenderBlock ) errorCallback
{
RCT_EXPORT ( ) ;
if ( _nextLayoutAnimation ) {
2015-03-01 23:33:55 +00:00
RCTLogWarn ( @ "Warning: Overriding previous layout animation with new one before the first began:\n%@ -> %@." ,
_nextLayoutAnimation , config ) ;
2015-02-20 04:10:52 +00:00
}
if ( config [ @ "delete" ] ! = nil ) {
2015-03-01 23:33:55 +00:00
RCTLogError ( @ "LayoutAnimation only supports create and update right now. Config: %@" , config ) ;
2015-02-20 04:10:52 +00:00
}
_nextLayoutAnimation = [ [ RCTLayoutAnimation alloc ] initWithDictionary : config callback : callback ] ;
}
2015-02-27 12:05:35 +00:00
- ( void ) startOrResetInteractionTiming
{
RCT_EXPORT ( ) ;
NSSet * rootViewTags = [ _rootViewTags copy ] ;
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
for ( NSNumber * reactTag in rootViewTags ) {
2015-03-25 00:37:03 +00:00
id rootView = viewRegistry [ reactTag ] ;
if ( [ rootView respondsToSelector : @ selector ( startOrResetInteractionTiming ) ] ) {
[ rootView startOrResetInteractionTiming ] ;
}
2015-02-27 12:05:35 +00:00
}
} ] ;
}
- ( void ) endAndResetInteractionTiming : ( RCTResponseSenderBlock ) onSuccess
onError : ( RCTResponseSenderBlock ) onError
{
RCT_EXPORT ( ) ;
NSSet * rootViewTags = [ _rootViewTags copy ] ;
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
NSMutableDictionary * timingData = [ [ NSMutableDictionary alloc ] init ] ;
for ( NSNumber * reactTag in rootViewTags ) {
2015-03-25 00:37:03 +00:00
id rootView = viewRegistry [ reactTag ] ;
if ( [ rootView respondsToSelector : @ selector ( endAndResetInteractionTiming ) ] ) {
2015-02-27 12:05:35 +00:00
timingData [ reactTag . stringValue ] = [ rootView endAndResetInteractionTiming ] ;
}
}
onSuccess ( @ [ timingData ] ) ;
} ] ;
}
2015-02-20 04:10:52 +00:00
static UIView * _jsResponder ;
+ ( UIView * ) JSResponder
{
return _jsResponder ;
}
@ end
2015-03-01 23:33:55 +00:00
@ implementation RCTBridge ( RCTUIManager )
- ( RCTUIManager * ) uiManager
{
return self . modules [ NSStringFromClass ( [ RCTUIManager class ] ) ] ;
}
@ end