2015-01-30 01:10:49 +00:00
// Copyright 2004 - present Facebook . All Rights Reserved .
# import "RCTUIManager.h"
# import < AVFoundation / AVFoundation . h >
# import < objc / message . h >
# import "Layout.h"
2015-02-19 01:39:09 +00:00
# import "RCTAnimationType.h"
2015-01-30 01:10:49 +00:00
# import "RCTAssert.h"
# import "RCTBridge.h"
# import "RCTConvert.h"
# import "RCTRootView.h"
# import "RCTLog.h"
# import "RCTNavigator.h"
# import "RCTScrollableProtocol.h"
# import "RCTShadowView.h"
# import "RCTSparseArray.h"
# import "RCTUtils.h"
# import "RCTView.h"
# import "RCTViewNodeProtocol.h"
2015-02-06 23:43:59 +00:00
# import "RCTViewManager.h"
2015-01-30 01:10:49 +00:00
# import "UIView+ReactKit.h"
typedef void ( ^ react_view _node _block _t ) ( id < RCTViewNodeProtocol > ) ;
2015-02-04 00:02:36 +00:00
static void RCTTraverseViewNodes ( id < RCTViewNodeProtocol > view , react_view _node _block _t block )
{
2015-01-30 01:10:49 +00:00
if ( view . reactTag ) block ( view ) ;
for ( id < RCTViewNodeProtocol > subview in view . reactSubviews ) {
RCTTraverseViewNodes ( subview , block ) ;
}
}
2015-02-04 00:12:07 +00:00
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 ;
}
2015-02-06 23:43:59 +00:00
if ( ! [ cls isSubclassOfClass : [ RCTViewManager class ] ] ) {
// Not a view module
2015-02-04 00:12:07 +00:00
continue ;
}
// Get module name
2015-02-06 23:43:59 +00:00
NSString * moduleName = [ cls moduleName ] ;
2015-02-04 00:12:07 +00:00
// Check module name is unique
id existingClass = modules [ moduleName ] ;
2015-02-06 23:43:59 +00:00
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
2015-02-04 00:12:07 +00:00
modules [ moduleName ] = cls ;
}
free ( classes ) ;
} ) ;
return modules ;
}
2015-02-19 01:39:09 +00:00
@ 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
UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType ( RCTAnimationType type )
{
switch ( type ) {
case RCTAnimationTypeLinear :
return UIViewAnimationCurveLinear ;
case RCTAnimationTypeEaseIn :
return UIViewAnimationCurveEaseIn ;
case RCTAnimationTypeEaseOut :
return UIViewAnimationCurveEaseOut ;
case RCTAnimationTypeEaseInEaseOut :
return UIViewAnimationCurveEaseInOut ;
default :
RCTCAssert ( NO , @ "Unsupported animation type %zd" , type ) ;
return UIViewAnimationCurveEaseInOut ;
}
}
- ( instancetype ) initWithDuration : ( NSTimeInterval ) duration dictionary : ( NSDictionary * ) config
{
if ( ! config ) {
return nil ;
}
if ( ( self = [ super init ] ) ) {
_property = [ RCTConvert NSString : config [ @ "property" ] ] ;
// TODO : this should be provided in ms , not seconds
_duration = [ RCTConvert NSTimeInterval : config [ @ "duration" ] ] ? : duration ;
_delay = [ RCTConvert NSTimeInterval : config [ @ "delay" ] ] ;
_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 ] ) ) {
// TODO : this should be provided in ms , not seconds
NSTimeInterval duration = [ RCTConvert NSTimeInterval : config [ @ "duration" ] ] ;
_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-01-30 01:10:49 +00:00
@ implementation RCTUIManager
{
// Root views are only mutated on the shadow queue
NSMutableSet * _rootViewTags ;
NSMutableArray * _pendingUIBlocks ;
2015-02-19 01:39:09 +00:00
NSLock * _pendingUIBlocksLock ;
2015-01-30 01:10:49 +00:00
2015-02-19 01:39:09 +00:00
// Animation
RCTLayoutAnimation * _nextLayoutAnimation ; // RCT thread only
RCTLayoutAnimation * _layoutAnimation ; // Main thread only
// Keyed by moduleName
NSMutableDictionary * _defaultShadowViews ; // RCT thread only
NSMutableDictionary * _defaultViews ; // Main thread only
NSDictionary * _viewManagers ;
2015-01-30 01:10:49 +00:00
2015-02-19 01:39:09 +00:00
// Keyed by React tag
RCTSparseArray * _viewManagerRegistry ; // RCT thread only
RCTSparseArray * _shadowViewRegistry ; // RCT thread only
RCTSparseArray * _viewRegistry ; // Main thread only
2015-02-04 00:12:07 +00:00
__weak RCTBridge * _bridge ;
2015-01-30 01:10:49 +00:00
}
2015-02-04 00:12:07 +00:00
- ( instancetype ) initWithBridge : ( RCTBridge * ) bridge
2015-01-30 01:10:49 +00:00
{
if ( ( self = [ super init ] ) ) {
2015-02-04 00:12:07 +00:00
_bridge = bridge ;
2015-02-19 01:39:09 +00:00
_pendingUIBlocksLock = [ [ NSLock alloc ] init ] ;
2015-01-30 01:10:49 +00:00
2015-02-04 00:12:07 +00:00
// Instantiate view managers
NSMutableDictionary * viewManagers = [ [ NSMutableDictionary alloc ] init ] ;
[ RCTViewModuleClasses ( ) enumerateKeysAndObjectsUsingBlock : ^ ( NSString * moduleName , Class moduleClass , BOOL * stop ) {
2015-02-04 00:15:20 +00:00
viewManagers [ moduleName ] = [ [ moduleClass alloc ] initWithEventDispatcher : _bridge . eventDispatcher ] ;
2015-02-04 00:12:07 +00:00
} ] ;
2015-01-30 01:10:49 +00:00
_viewManagers = viewManagers ;
2015-02-19 01:39:09 +00:00
_defaultShadowViews = [ [ NSMutableDictionary alloc ] init ] ;
_defaultViews = [ [ NSMutableDictionary alloc ] init ] ;
2015-02-04 00:12:07 +00:00
2015-02-19 01:39:09 +00:00
_viewManagerRegistry = [ [ RCTSparseArray alloc ] init ] ;
2015-01-30 01:10:49 +00:00
_shadowViewRegistry = [ [ RCTSparseArray alloc ] init ] ;
2015-02-19 01:39:09 +00:00
_viewRegistry = [ [ RCTSparseArray alloc ] init ] ;
2015-02-04 00:12:07 +00:00
2015-01-30 01:10:49 +00:00
// Internal resources
_pendingUIBlocks = [ [ NSMutableArray alloc ] init ] ;
_rootViewTags = [ [ NSMutableSet alloc ] init ] ;
}
return self ;
}
- ( instancetype ) init
{
RCT_NOT _DESIGNATED _INITIALIZER ( ) ;
}
- ( void ) dealloc
{
RCTAssert ( ! self . valid , @ "must call -invalidate before -dealloc" ) ;
}
- ( BOOL ) isValid
{
return _viewRegistry ! = nil ;
}
- ( void ) invalidate
{
2015-02-04 00:12:07 +00:00
RCTAssertMainThread ( ) ;
2015-01-30 01:10:49 +00:00
_viewRegistry = nil ;
_shadowViewRegistry = nil ;
2015-02-19 01:39:09 +00:00
[ _pendingUIBlocksLock lock ] ;
2015-01-30 01:10:49 +00:00
_pendingUIBlocks = nil ;
2015-02-19 01:39:09 +00:00
[ _pendingUIBlocksLock unlock ] ;
2015-01-30 01:10:49 +00:00
}
- ( void ) registerRootView : ( RCTRootView * ) rootView ;
{
2015-02-04 00:12:07 +00:00
RCTAssertMainThread ( ) ;
2015-01-30 01:10:49 +00:00
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 ;
2015-02-19 01:39:09 +00:00
// Register manager ( TODO : should we do this , or leave it nil ? )
_viewManagerRegistry [ reactTag ] = _viewManagers [ [ RCTViewManager moduleName ] ] ;
2015-01-30 01:10:49 +00:00
// Register shadow view
2015-02-04 00:12:07 +00:00
dispatch_async ( _bridge . shadowQueue , ^ {
2015-01-30 01:10:49 +00:00
RCTShadowView * shadowView = [ [ RCTShadowView alloc ] init ] ;
shadowView . reactTag = reactTag ;
shadowView . frame = frame ;
shadowView . backgroundColor = [ UIColor whiteColor ] ;
shadowView . reactRootView = YES ; // can this just be inferred from the fact that it has no superview ?
_shadowViewRegistry [ shadowView . reactTag ] = shadowView ;
[ _rootViewTags addObject : reactTag ] ;
} ) ;
}
/ * *
* Unregisters views from registries
* /
- ( void ) _purgeChildren : ( NSArray * ) children fromRegistry : ( RCTSparseArray * ) registry
{
for ( id < RCTViewNodeProtocol > child in children ) {
RCTTraverseViewNodes ( registry [ child . reactTag ] , ^ ( id < RCTViewNodeProtocol > subview ) {
2015-02-19 01:39:09 +00:00
RCTAssert ( ! [ subview isReactRootView ] , @ "Root views should not be unregistered" ) ;
2015-02-06 23:43:59 +00:00
if ( [ subview conformsToProtocol : @ protocol ( RCTInvalidating ) ] ) {
[ ( id < RCTInvalidating > ) subview invalidate ] ;
2015-01-30 01:10:49 +00:00
}
registry [ subview . reactTag ] = nil ;
} ) ;
}
}
- ( void ) addUIBlock : ( RCTViewManagerUIBlock ) block
{
RCTAssert ( ! [ NSThread isMainThread ] , @ "This method should only be called on the shadow thread" ) ;
2015-02-19 01:39:09 +00:00
2015-01-30 01:10:49 +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-02-19 01:39:09 +00:00
[ _pendingUIBlocksLock lock ] ;
[ _pendingUIBlocks addObject : outerBlock ] ;
[ _pendingUIBlocksLock unlock ] ;
2015-01-30 01:10:49 +00:00
}
2015-02-19 01:39:09 +00:00
- ( RCTViewManagerUIBlock ) uiBlockWithLayoutUpdateForRootView : ( RCTShadowView * ) rootShadowView
2015-01-30 01:10:49 +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
2015-02-19 01:39:09 +00:00
// the main thread .
[ rootShadowView collectRootUpdatedFrames : viewsWithNewFrames parentConstraint : ( CGSize ) { CSS_UNDEFINED , CSS_UNDEFINED } ] ;
2015-01-30 01:10:49 +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-02-19 01:39:09 +00:00
2015-01-30 01:10:49 +00:00
for ( RCTShadowView * shadowView in viewsWithNewFrames ) {
// We have to do this after we build the parentsAreNew array .
shadowView . newView = NO ;
}
2015-02-19 01:39:09 +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 ] ;
2015-01-30 01:10:49 +00:00
UIView * view = viewRegistry [ reactTag ] ;
2015-02-19 01:39:09 +00:00
CGRect frame = [ frames [ ii ] CGRectValue ] ;
2015-01-30 01:10:49 +00:00
// These frames are in terms of anchorPoint = topLeft , but internally the
// views are anchorPoint = center for easier scale and rotation animations .
// Convert the frame so it works with anchorPoint = center .
2015-02-19 01:39:09 +00:00
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 ) ) ;
continue ;
}
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 : ^ {
view . layer . position = position ;
view . layer . bounds = bounds ;
} withCompletionBlock : completion ] ;
} else {
view . layer . position = position ;
view . layer . bounds = bounds ;
completion ( YES ) ;
}
// Animate view creations
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 {
RCTLogError ( @ "Unsupported layout animation createConfig property %@" , createAnimation . property ) ;
}
} withCompletionBlock : nil ] ;
}
2015-01-30 01:10:49 +00:00
}
2015-02-19 01:39:09 +00:00
2015-01-30 01:10:49 +00:00
RCTRootView * rootView = _viewRegistry [ rootViewTag ] ;
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-02-06 23:43:59 +00:00
2015-02-19 01:39:09 +00:00
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
2015-02-06 23:43:59 +00:00
for ( RCTApplierBlock block in applierBlocks ) {
block ( viewRegistry ) ;
2015-01-30 01:10:49 +00:00
}
} ] ;
}
/ * *
* 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 ) ;
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 ) ;
[ self _purgeChildren : @ [ rootShadowView ] fromRegistry : _shadowViewRegistry ] ;
[ _rootViewTags removeObject : rootReactTag ] ;
2015-02-19 01:39:09 +00:00
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
2015-01-30 01:10:49 +00:00
RCTCAssertMainThread ( ) ;
UIView * rootView = viewRegistry [ rootReactTag ] ;
2015-02-19 01:39:09 +00:00
[ uiManager _purgeChildren : @ [ rootView ] fromRegistry : viewRegistry ] ;
2015-01-30 01:10:49 +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 ] ;
2015-02-19 01:39:09 +00:00
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
[ uiManager _manageChildren : containerReactTag
moveFromIndices : moveFromIndices
moveToIndices : moveToIndices
addChildReactTags : addChildReactTags
addAtIndices : addAtIndices
removeAtIndices : removeAtIndices
registry : viewRegistry ] ;
2015-01-30 01:10:49 +00:00
} ] ;
}
- ( 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 ] ] ;
}
}
2015-02-06 23:43:59 +00:00
static BOOL RCTCallPropertySetter ( SEL setter , id value , id view , id defaultView , RCTViewManager * manager )
2015-01-30 01:10:49 +00:00
{
// 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 ;
}
return NO ;
}
static void RCTSetViewProps ( NSDictionary * props , UIView * view ,
2015-02-06 23:43:59 +00:00
UIView * defaultView , RCTViewManager * manager )
2015-01-30 01:10:49 +00:00
{
[ 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 ) ;
} ] ;
}
static void RCTSetShadowViewProps ( NSDictionary * props , RCTShadowView * shadowView ,
2015-02-06 23:43:59 +00:00
RCTShadowView * defaultView , RCTViewManager * manager )
2015-01-30 01:10:49 +00:00
{
[ 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
2015-02-04 00:15:20 +00:00
// Note : not just doing ` [ defaultView valueForKey : key ] ` , the
// key may not exist , in which case we ' d get an exception .
RCTCopyProperty ( shadowView , defaultView , key ) ;
2015-01-30 01:10:49 +00:00
} else {
RCTSetProperty ( shadowView , key , obj ) ;
}
}
} ] ;
// Update layout
[ shadowView updateShadowViewLayout ] ;
}
- ( void ) createAndRegisterViewWithReactTag : ( NSNumber * ) reactTag
moduleName : ( NSString * ) moduleName
props : ( NSDictionary * ) props
{
RCT_EXPORT ( createView ) ;
2015-02-19 01:39:09 +00:00
RCTViewManager * manager = _viewManagers [ moduleName ] ;
if ( manager = = nil ) {
RCTLogWarn ( @ "No manager class found for view with module name \" % @ \ "" , moduleName ) ;
manager = [ [ RCTViewManager alloc ] init ] ;
}
2015-01-30 01:10:49 +00:00
// Generate default view , used for resetting default props
if ( ! _defaultShadowViews [ moduleName ] ) {
2015-02-06 23:43:59 +00:00
_defaultShadowViews [ moduleName ] = [ manager shadowView ] ;
2015-01-30 01:10:49 +00:00
}
2015-02-06 23:43:59 +00:00
RCTShadowView * shadowView = [ manager shadowView ] ;
2015-01-30 01:10:49 +00:00
shadowView . moduleName = moduleName ;
shadowView . reactTag = reactTag ;
RCTSetShadowViewProps ( props , shadowView , _defaultShadowViews [ moduleName ] , manager ) ;
_shadowViewRegistry [ shadowView . reactTag ] = shadowView ;
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
RCTCAssertMainThread ( ) ;
2015-02-19 01:46:39 +00:00
// Register manager
2015-02-19 01:39:09 +00:00
uiManager -> _viewManagerRegistry [ reactTag ] = manager ;
2015-01-30 01:10:49 +00:00
// Generate default view , used for resetting default props
if ( ! uiManager -> _defaultViews [ moduleName ] ) {
// 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-02-04 00:15:20 +00:00
uiManager -> _defaultViews [ moduleName ] = [ manager view ] ;
2015-01-30 01:10:49 +00:00
}
2015-02-04 00:15:20 +00:00
UIView * view = [ manager view ] ;
2015-01-30 01:10:49 +00:00
if ( view ) {
2015-02-19 01:39:09 +00:00
2015-01-30 01:10:49 +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
// Set custom properties
RCTSetViewProps ( props , view , uiManager -> _defaultViews [ moduleName ] , manager ) ;
}
viewRegistry [ view . reactTag ] = view ;
} ] ;
}
2015-02-19 01:39:09 +00:00
// TODO : remove moduleName param as it isn ' t needed
- ( void ) updateView : ( NSNumber * ) reactTag moduleName : ( __unused NSString * ) _ props : ( NSDictionary * ) props
2015-01-30 01:10:49 +00:00
{
RCT_EXPORT ( ) ;
2015-02-19 01:39:09 +00:00
RCTViewManager * viewManager = _viewManagerRegistry [ reactTag ] ;
NSString * moduleName = [ [ viewManager class ] moduleName ] ;
2015-01-30 01:10:49 +00:00
RCTShadowView * shadowView = _shadowViewRegistry [ reactTag ] ;
2015-02-19 01:39:09 +00:00
RCTSetShadowViewProps ( props , shadowView , _defaultShadowViews [ moduleName ] , viewManager ) ;
2015-01-30 01:10:49 +00:00
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
2015-02-19 01:39:09 +00:00
UIView * view = uiManager -> _viewRegistry [ reactTag ] ;
RCTSetViewProps ( props , view , uiManager -> _defaultViews [ moduleName ] , viewManager ) ;
2015-01-30 01:10:49 +00:00
} ] ;
}
- ( void ) becomeResponder : ( NSNumber * ) reactTag
{
RCT_EXPORT ( focus ) ;
if ( ! reactTag ) return ;
2015-02-19 01:39:09 +00:00
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
2015-01-30 01:10:49 +00:00
UIView * newResponder = viewRegistry [ reactTag ] ;
[ newResponder becomeFirstResponder ] ;
} ] ;
}
- ( void ) resignResponder : ( NSNumber * ) reactTag
{
RCT_EXPORT ( blur ) ;
if ( ! reactTag ) return ;
2015-02-19 01:39:09 +00:00
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
2015-01-30 01:10:49 +00:00
UIView * currentResponder = viewRegistry [ reactTag ] ;
[ currentResponder resignFirstResponder ] ;
} ] ;
}
- ( 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 .
2015-02-06 23:43:59 +00:00
for ( RCTViewManager * manager in _viewManagers . allValues ) {
RCTViewManagerUIBlock uiBlock = [ manager uiBlockToAmendWithShadowViewRegistry : _shadowViewRegistry ] ;
2015-02-19 01:39:09 +00:00
if ( uiBlock ) {
2015-01-30 01:10:49 +00:00
[ self addUIBlock : uiBlock ] ;
}
}
2015-02-04 00:12:07 +00:00
2015-02-19 01:39:09 +00:00
// Set up next layout animation
if ( _nextLayoutAnimation ) {
RCTLayoutAnimation * layoutAnimation = _nextLayoutAnimation ;
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
uiManager -> _layoutAnimation = layoutAnimation ;
} ] ;
}
// Perform layout
2015-01-30 01:10:49 +00:00
for ( NSNumber * reactTag in _rootViewTags ) {
RCTShadowView * rootView = _shadowViewRegistry [ reactTag ] ;
[ self addUIBlock : [ self uiBlockWithLayoutUpdateForRootView : rootView ] ] ;
[ self _amendPendingUIBlocksWithStylePropagationUpdateForRootView : rootView ] ;
}
2015-02-04 00:12:07 +00:00
2015-02-19 01:39:09 +00:00
// Clear layout animations
if ( _nextLayoutAnimation ) {
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
uiManager -> _layoutAnimation = nil ;
} ] ;
_nextLayoutAnimation = nil ;
}
[ _pendingUIBlocksLock lock ] ;
2015-01-30 01:10:49 +00:00
NSArray * previousPendingUIBlocks = _pendingUIBlocks ;
_pendingUIBlocks = [ [ NSMutableArray alloc ] init ] ;
2015-02-19 01:39:09 +00:00
[ _pendingUIBlocksLock unlock ] ;
2015-02-04 00:12:07 +00:00
2015-01-30 01:10:49 +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 ;
}
2015-02-19 01:39:09 +00:00
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
2015-01-30 01:10:49 +00:00
UIView * view = viewRegistry [ reactTag ] ;
if ( ! view ) {
RCTLogError ( @ "measure cannot find view with tag %zd" , reactTag ) ;
return ;
}
CGRect frame = view . frame ;
UIView * rootView = view ;
while ( rootView && ! [ rootView isReactRootView ] ) {
rootView = rootView . superview ;
}
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 ] ;
callback ( @ [
@ ( frame . origin . x ) ,
@ ( frame . origin . y ) ,
@ ( frame . size . width ) ,
@ ( frame . size . height ) ,
@ ( pagePoint . x ) ,
@ ( pagePoint . y )
] ) ;
} ] ;
}
- ( void ) requestSchedulingJavaScriptNavigation : ( NSNumber * ) reactTag
errorCallback : ( RCTResponseSenderBlock ) errorCallback
callback : ( RCTResponseSenderBlock ) callback
{
RCT_EXPORT ( ) ;
if ( ! callback || ! errorCallback ) {
RCTLogError ( @ "Callback not provided for navigation scheduling." ) ;
return ;
}
2015-02-19 01:39:09 +00:00
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
2015-01-30 01:10:49 +00:00
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`
* traversal - which is possibly more difficult than it sounds since a
* ` superview` is not a "react superview" .
* /
+ ( void ) measureLayoutOnNodes : ( RCTShadowView * ) view
ancestor : ( RCTShadowView * ) ancestor
errorCallback : ( RCTResponseSenderBlock ) errorCallback
callback : ( RCTResponseSenderBlock ) callback
{
if ( ! view ) {
NSString * msg = [ NSString stringWithFormat : @ "Attempting to measure view that does not exist %@" , view ] ;
errorCallback ( @ [ RCTAPIErrorObject ( msg ) ] ) ;
return ;
}
if ( ! ancestor ) {
NSString * msg = [ NSString stringWithFormat : @ "Attempting to measure relative to ancestor that does not exist %@" , ancestor ] ;
errorCallback ( @ [ RCTAPIErrorObject ( msg ) ] ) ;
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 ) ] ) ;
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 ) ) {
errorCallback ( @ [ RCTAPIErrorObject ( @ "Attempted to measure layout but offset or dimensions were NaN" ) ] ) ;
return ;
}
callback ( @ [ @ ( topOffset ) , @ ( leftOffset ) , @ ( width ) , @ ( height ) ] ) ;
}
/ * *
* 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 ] ;
[ RCTUIManager measureLayoutOnNodes : shadowView ancestor : ancestorShadowView errorCallback : errorCallback callback : callback ] ;
}
/ * *
* 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 ] ;
[ RCTUIManager measureLayoutOnNodes : shadowView ancestor : [ shadowView superview ] errorCallback : errorCallback callback : callback ] ;
}
/ * *
* 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 ) {
NSString * msg = [ NSString stringWithFormat : @ "Attempting to measure view that does not exist %@" , shadowView ] ;
errorCallback ( @ [ RCTAPIErrorObject ( msg ) ] ) ;
return ;
}
NSArray * childShadowViews = [ shadowView reactSubviews ] ;
NSMutableArray * results = [ [ NSMutableArray alloc ] initWithCapacity : [ childShadowViews count ] ] ;
CGRect layoutRect = [ RCTConvert CGRect : rect ] ;
for ( int ii = 0 ; ii < [ childShadowViews count ] ; ii + + ) {
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 ) ] ) ;
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
NSDictionary * result = @ { @ "index" : @ ( ii ) ,
@ "left" : @ ( leftOffset ) ,
@ "top" : @ ( topOffset ) ,
@ "width" : @ ( width ) ,
@ "height" : @ ( height ) } ;
[ results addObject : result ] ;
}
}
callback ( @ [ results ] ) ;
}
- ( void ) setMainScrollViewTag : ( NSNumber * ) reactTag
{
RCT_EXPORT ( ) ;
2015-02-19 01:39:09 +00:00
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
2015-01-30 01:10:49 +00:00
// - 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` .
2015-02-19 01:39:09 +00:00
if ( uiManager . mainScrollView ) {
uiManager . mainScrollView . nativeMainScrollDelegate = nil ;
2015-01-30 01:10:49 +00:00
}
if ( reactTag ) {
id rkObject = viewRegistry [ reactTag ] ;
if ( [ rkObject conformsToProtocol : @ protocol ( RCTScrollableProtocol ) ] ) {
2015-02-19 01:39:09 +00:00
uiManager . mainScrollView = ( id < RCTScrollableProtocol > ) rkObject ;
( ( id < RCTScrollableProtocol > ) rkObject ) . nativeMainScrollDelegate = uiManager . nativeMainScrollDelegate ;
2015-01-30 01:10:49 +00:00
} else {
RCTCAssert ( NO , @ "Tag %@ does not conform to RCTScrollableProtocol" , reactTag ) ;
}
} else {
2015-02-19 01:39:09 +00:00
uiManager . mainScrollView = nil ;
2015-01-30 01:10:49 +00:00
}
} ] ;
}
- ( void ) scrollToOffsetWithView : ( NSNumber * ) reactTag scrollToOffsetX : ( NSNumber * ) offsetX offsetY : ( NSNumber * ) offsetY
{
RCT_EXPORT ( scrollTo ) ;
2015-02-19 01:39:09 +00:00
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
2015-01-30 01:10:49 +00:00
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 ) ;
2015-02-19 01:39:09 +00:00
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
2015-01-30 01:10:49 +00:00
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 ) ;
}
} ] ;
}
- ( void ) getScrollViewContentSize : ( NSNumber * ) reactTag callback : ( RCTResponseSenderBlock ) callback failCallback : ( RCTResponseSenderBlock ) failCallback
{
RCT_EXPORT ( ) ;
2015-02-19 01:39:09 +00:00
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
2015-01-30 01:10:49 +00:00
UIView * view = viewRegistry [ reactTag ] ;
if ( ! view ) {
NSString * error = [ [ NSString alloc ] initWithFormat : @ "cannot find view with tag %@" , reactTag ] ;
RCTLogError ( @ "%@" , error ) ;
failCallback ( @ [ @ { @ "error" : error } ] ) ;
return ;
}
CGSize size = ( ( id < RCTScrollableProtocol > ) view ) . contentSize ;
NSDictionary * dict = @ { @ "width" : @ ( size . width ) , @ "height" : @ ( size . height ) } ;
callback ( @ [ dict ] ) ;
} ] ;
}
/ * *
* 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 ( ) ;
2015-02-19 01:39:09 +00:00
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
2015-01-30 01:10:49 +00:00
_jsResponder = viewRegistry [ reactTag ] ;
if ( ! _jsResponder ) {
RCTLogMustFix ( @ "Invalid view set to be the JS responder - tag %zd" , reactTag ) ;
}
} ] ;
}
- ( void ) clearJSResponder
{
RCT_EXPORT ( ) ;
2015-02-19 01:39:09 +00:00
[ self addUIBlock : ^ ( RCTUIManager * uiManager , RCTSparseArray * viewRegistry ) {
2015-01-30 01:10:49 +00:00
_jsResponder = nil ;
} ] ;
}
2015-02-04 00:15:20 +00:00
+ ( NSDictionary * ) allBubblingEventTypesConfigs
2015-01-30 01:10:49 +00:00
{
NSMutableDictionary * customBubblingEventTypesConfigs = [ @ {
// Bubble dispatched events
@ "topTap" : @ {
@ "phasedRegistrationNames" : @ {
@ "bubbled" : @ "notActuallyTapDontUseMe" ,
@ "captured" : @ "notActuallyTapCaptureDontUseMe"
}
} ,
@ "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-02-04 00:15:20 +00:00
[ RCTViewModuleClasses ( ) 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 ) ;
2015-01-30 01:10:49 +00:00
}
2015-02-04 00:15:20 +00:00
[ customBubblingEventTypesConfigs addEntriesFromDictionary : eventTypes ] ;
2015-01-30 01:10:49 +00:00
}
2015-02-04 00:15:20 +00:00
} ] ;
2015-01-30 01:10:49 +00:00
return customBubblingEventTypesConfigs ;
}
2015-02-04 00:15:20 +00:00
+ ( NSDictionary * ) allDirectEventTypesConfigs
2015-01-30 01:10:49 +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-02-04 00:15:20 +00:00
[ RCTViewModuleClasses ( ) enumerateKeysAndObjectsUsingBlock : ^ ( NSString * name , Class cls , BOOL * stop ) {
if ( RCTClassOverridesClassMethod ( cls , @ selector ( customDirectEventTypes ) ) ) {
NSDictionary * eventTypes = [ cls customDirectEventTypes ] ;
for ( NSString * eventName in eventTypes ) {
RCTCAssert ( ! customDirectEventTypes [ eventName ] , @ "Event '%@' registered multiple times." , eventName ) ;
2015-01-30 01:10:49 +00:00
}
2015-02-04 00:15:20 +00:00
[ customDirectEventTypes addEntriesFromDictionary : eventTypes ] ;
2015-01-30 01:10:49 +00:00
}
2015-02-04 00:15:20 +00:00
} ] ;
2015-01-30 01:10:49 +00:00
return customDirectEventTypes ;
}
2015-02-04 00:15:20 +00:00
+ ( NSDictionary * ) constantsToExport
2015-01-30 01:10:49 +00:00
{
NSMutableDictionary * allJSConstants = [ @ {
@ "customBubblingEventTypes" : [ self allBubblingEventTypesConfigs ] ,
@ "customDirectEventTypes" : [ self allDirectEventTypesConfigs ] ,
@ "NSTextAlignment" : @ {
@ "Left" : @ ( NSTextAlignmentLeft ) ,
@ "Center" : @ ( NSTextAlignmentCenter ) ,
@ "Right" : @ ( NSTextAlignmentRight ) ,
} ,
@ "Dimensions" : @ {
@ "window" : @ {
2015-02-04 00:15:20 +00:00
@ "width" : @ ( RCTScreenSize ( ) . width ) ,
@ "height" : @ ( RCTScreenSize ( ) . height ) ,
@ "scale" : @ ( RCTScreenScale ( ) ) ,
2015-01-30 01:10:49 +00:00
} ,
@ "modalFullscreenView" : @ {
2015-02-04 00:15:20 +00:00
@ "width" : @ ( RCTScreenSize ( ) . width ) ,
@ "height" : @ ( RCTScreenSize ( ) . width ) ,
2015-01-30 01:10:49 +00:00
} ,
} ,
@ "StyleConstants" : @ {
@ "PointerEventsValues" : @ {
@ "none" : @ ( RCTPointerEventsNone ) ,
@ "boxNone" : @ ( RCTPointerEventsBoxNone ) ,
@ "boxOnly" : @ ( RCTPointerEventsBoxOnly ) ,
@ "unspecified" : @ ( RCTPointerEventsUnspecified ) ,
} ,
} ,
@ "UIText" : @ {
@ "AutocapitalizationType" : @ {
@ "AllCharacters" : @ ( UITextAutocapitalizationTypeAllCharacters ) ,
@ "Sentences" : @ ( UITextAutocapitalizationTypeSentences ) ,
@ "Words" : @ ( UITextAutocapitalizationTypeWords ) ,
@ "None" : @ ( UITextAutocapitalizationTypeNone ) ,
} ,
} ,
@ "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-02-04 00:15:20 +00:00
[ RCTViewModuleClasses ( ) 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 ) ;
2015-01-30 01:10:49 +00:00
// add an additional ' Constants ' namespace for each class
2015-02-04 00:15:20 +00:00
namespace [ @ "Constants" ] = constants ;
allJSConstants [ name ] = [ namespace copy ] ;
2015-01-30 01:10:49 +00:00
}
} ] ;
return allJSConstants ;
}
2015-02-19 01:39:09 +00:00
- ( void ) configureNextLayoutAnimation : ( NSDictionary * ) config
withCallback : ( RCTResponseSenderBlock ) callback
errorCallback : ( RCTResponseSenderBlock ) errorCallback
2015-01-30 01:10:49 +00:00
{
RCT_EXPORT ( ) ;
2015-02-19 01:39:09 +00:00
if ( _nextLayoutAnimation ) {
RCTLogWarn ( @ "Warning: Overriding previous layout animation with new one before the first began:\n%@ -> %@." , _nextLayoutAnimation , config ) ;
2015-01-30 01:10:49 +00:00
}
if ( config [ @ "delete" ] ! = nil ) {
RCTLogError ( @ "LayoutAnimation only supports create and update right now. Config: %@" , config ) ;
}
2015-02-19 01:39:09 +00:00
_nextLayoutAnimation = [ [ RCTLayoutAnimation alloc ] initWithDictionary : config callback : callback ] ;
2015-01-30 01:10:49 +00:00
}
static UIView * _jsResponder ;
+ ( UIView * ) JSResponder
{
return _jsResponder ;
}
@ end