1 | #import "REANodesManager.h"
|
2 |
|
3 | #import <React/RCTConvert.h>
|
4 |
|
5 | #import "Nodes/REANode.h"
|
6 | #import "Nodes/REAPropsNode.h"
|
7 | #import "Nodes/REAStyleNode.h"
|
8 | #import "Nodes/REATransformNode.h"
|
9 | #import "Nodes/REAValueNode.h"
|
10 | #import "Nodes/REABlockNode.h"
|
11 | #import "Nodes/REACondNode.h"
|
12 | #import "Nodes/REAOperatorNode.h"
|
13 | #import "Nodes/REASetNode.h"
|
14 | #import "Nodes/READebugNode.h"
|
15 | #import "Nodes/REAClockNodes.h"
|
16 | #import "Nodes/REAJSCallNode.h"
|
17 | #import "Nodes/REABezierNode.h"
|
18 | #import "Nodes/REAEventNode.h"
|
19 | #import "REAModule.h"
|
20 | #import "Nodes/REAAlwaysNode.h"
|
21 | #import "Nodes/REAConcatNode.h"
|
22 | #import "Nodes/REAParamNode.h"
|
23 | #import "Nodes/REAFunctionNode.h"
|
24 | #import "Nodes/REACallFuncNode.h"
|
25 | #import <React/RCTShadowView.h>
|
26 |
|
27 | // Interface below has been added in order to use private methods of RCTUIManager,
|
28 | // RCTUIManager#UpdateView is a React Method which is exported to JS but in
|
29 | // Objective-C it stays private
|
30 | // RCTUIManager#setNeedsLayout is a method which updated layout only which
|
31 | // in its turn will trigger relayout if no batch has been activated
|
32 |
|
33 | @interface RCTUIManager ()
|
34 |
|
35 | - (void)updateView:(nonnull NSNumber *)reactTag
|
36 | viewName:(NSString *)viewName
|
37 | props:(NSDictionary *)props;
|
38 |
|
39 | - (void)setNeedsLayout;
|
40 |
|
41 | @end
|
42 |
|
43 | @interface RCTUIManager (SyncUpdates)
|
44 |
|
45 | - (BOOL)hasEnqueuedUICommands;
|
46 |
|
47 | - (void)runSyncUIUpdatesWithObserver:(id<RCTUIManagerObserver>)observer;
|
48 |
|
49 | @end
|
50 |
|
51 | @implementation RCTUIManager (SyncUpdates)
|
52 |
|
53 | - (BOOL)hasEnqueuedUICommands
|
54 | {
|
55 | // Accessing some private bits of RCTUIManager to provide missing functionality
|
56 | return [[self valueForKey:@"_pendingUIBlocks"] count] > 0;
|
57 | }
|
58 |
|
59 | - (void)runSyncUIUpdatesWithObserver:(id<RCTUIManagerObserver>)observer
|
60 | {
|
61 | // before we run uimanager batch complete, we override coordinator observers list
|
62 | // to avoid observers from firing. This is done because we only want the uimanager
|
63 | // related operations to run and not all other operations (including the ones enqueued
|
64 | // by reanimated or native animated modules) from being scheduled. If we were to allow
|
65 | // other modules to execute some logic from this sync uimanager run there is a possibility
|
66 | // that the commands will execute out of order or that we intercept a batch of commands that
|
67 | // those modules may be in a middle of (we verify that batch isn't in progress for uimodule
|
68 | // but can't do the same for all remaining modules)
|
69 |
|
70 | // store reference to the observers array
|
71 | id oldObservers = [self.observerCoordinator valueForKey:@"_observers"];
|
72 |
|
73 | // temporarily replace observers with a table conatining just nodesmanager (we need
|
74 | // this to capture mounting block)
|
75 | NSHashTable<id<RCTUIManagerObserver>> *soleObserver = [NSHashTable new];
|
76 | [soleObserver addObject:observer];
|
77 | [self.observerCoordinator setValue:soleObserver forKey:@"_observers"];
|
78 |
|
79 | // run batch
|
80 | [self batchDidComplete];
|
81 | // restore old observers table
|
82 | [self.observerCoordinator setValue:oldObservers forKey:@"_observers"];
|
83 | }
|
84 |
|
85 | @end
|
86 |
|
87 | @interface REANodesManager() <RCTUIManagerObserver>
|
88 |
|
89 | @end
|
90 |
|
91 |
|
92 | @implementation REANodesManager
|
93 | {
|
94 | NSMutableDictionary<REANodeID, REANode *> *_nodes;
|
95 | NSMapTable<NSString *, REANode *> *_eventMapping;
|
96 | NSMutableArray<id<RCTEvent>> *_eventQueue;
|
97 | CADisplayLink *_displayLink;
|
98 | REAUpdateContext *_updateContext;
|
99 | BOOL _wantRunUpdates;
|
100 | BOOL _processingDirectEvent;
|
101 | NSMutableArray<REAOnAnimationCallback> *_onAnimationCallbacks;
|
102 | NSMutableArray<REANativeAnimationOp> *_operationsInBatch;
|
103 | BOOL _tryRunBatchUpdatesSynchronously;
|
104 | REAEventHandler _eventHandler;
|
105 | volatile void (^_mounting)(void);
|
106 | }
|
107 |
|
108 | - (instancetype)initWithModule:(REAModule *)reanimatedModule
|
109 | uiManager:(RCTUIManager *)uiManager
|
110 | {
|
111 | if ((self = [super init])) {
|
112 | _reanimatedModule = reanimatedModule;
|
113 | _uiManager = uiManager;
|
114 | _nodes = [NSMutableDictionary new];
|
115 | _eventMapping = [NSMapTable strongToWeakObjectsMapTable];
|
116 | _eventQueue = [NSMutableArray new];
|
117 | _updateContext = [REAUpdateContext new];
|
118 | _wantRunUpdates = NO;
|
119 | _onAnimationCallbacks = [NSMutableArray new];
|
120 | _operationsInBatch = [NSMutableArray new];
|
121 | }
|
122 |
|
123 | _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onAnimationFrame:)];
|
124 | [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
125 | [_displayLink setPaused:true];
|
126 | return self;
|
127 | }
|
128 |
|
129 | - (void)invalidate
|
130 | {
|
131 | _eventHandler = nil;
|
132 | [self stopUpdatingOnAnimationFrame];
|
133 | }
|
134 |
|
135 | - (void)operationsBatchDidComplete
|
136 | {
|
137 | if (![_displayLink isPaused]) {
|
138 | // if display link is set it means some of the operations that have run as a part of the batch
|
139 | // requested updates. We want updates to be run in the same frame as in which operations have
|
140 | // been scheduled as it may mean the new view has just been mounted and expects its initial
|
141 | // props to be calculated.
|
142 | // Unfortunately if the operation has just scheduled animation callback it won't run until the
|
143 | // next frame, so it's being triggered manually.
|
144 | _wantRunUpdates = YES;
|
145 | [self performOperations];
|
146 | }
|
147 | }
|
148 |
|
149 | - (REANode *)findNodeByID:(REANodeID)nodeID
|
150 | {
|
151 | return _nodes[nodeID];
|
152 | }
|
153 |
|
154 | - (void)postOnAnimation:(REAOnAnimationCallback)clb
|
155 | {
|
156 | [_onAnimationCallbacks addObject:clb];
|
157 | [self startUpdatingOnAnimationFrame];
|
158 | }
|
159 |
|
160 | - (void)postRunUpdatesAfterAnimation
|
161 | {
|
162 | _wantRunUpdates = YES;
|
163 | if (!_processingDirectEvent) {
|
164 | [self startUpdatingOnAnimationFrame];
|
165 | }
|
166 | }
|
167 |
|
168 | - (void)registerEventHandler:(REAEventHandler)eventHandler
|
169 | {
|
170 | _eventHandler = eventHandler;
|
171 | }
|
172 |
|
173 | - (void)startUpdatingOnAnimationFrame
|
174 | {
|
175 | // Setting _currentAnimationTimestamp here is connected with manual triggering of performOperations
|
176 | // in operationsBatchDidComplete. If new node has been created and clock has not been started,
|
177 | // _displayLink won't be initialized soon enough and _displayLink.timestamp will be 0.
|
178 | // However, CADisplayLink is using CACurrentMediaTime so if there's need to perform one more
|
179 | // evaluation, it could be used it here. In usual case, CACurrentMediaTime is not being used in
|
180 | // favor of setting it with _displayLink.timestamp in onAnimationFrame method.
|
181 | _currentAnimationTimestamp = CACurrentMediaTime();
|
182 | [_displayLink setPaused:false];
|
183 | }
|
184 |
|
185 | - (void)stopUpdatingOnAnimationFrame
|
186 | {
|
187 | if (_displayLink) {
|
188 | [_displayLink setPaused:true];
|
189 | }
|
190 | }
|
191 |
|
192 | - (void)onAnimationFrame:(CADisplayLink *)displayLink
|
193 | {
|
194 | _currentAnimationTimestamp = _displayLink.timestamp;
|
195 |
|
196 | // We process all enqueued events first
|
197 | for (NSUInteger i = 0; i < _eventQueue.count; i++) {
|
198 | id<RCTEvent> event = _eventQueue[i];
|
199 | [self processEvent:event];
|
200 | }
|
201 | [_eventQueue removeAllObjects];
|
202 |
|
203 | NSArray<REAOnAnimationCallback> *callbacks = _onAnimationCallbacks;
|
204 | _onAnimationCallbacks = [NSMutableArray new];
|
205 |
|
206 | // When one of the callbacks would postOnAnimation callback we don't want
|
207 | // to process it until the next frame. This is why we cpy the array before
|
208 | // we iterate over it
|
209 | for (REAOnAnimationCallback block in callbacks) {
|
210 | block(displayLink);
|
211 | }
|
212 |
|
213 | [self performOperations];
|
214 |
|
215 | if (_onAnimationCallbacks.count == 0) {
|
216 | [self stopUpdatingOnAnimationFrame];
|
217 | }
|
218 | }
|
219 |
|
220 | - (BOOL)uiManager:(RCTUIManager *)manager performMountingWithBlock:(RCTUIManagerMountingBlock)block {
|
221 | RCTAssert(_mounting == nil, @"Mouting block is expected to not be set");
|
222 | _mounting = block;
|
223 | return YES;
|
224 | }
|
225 |
|
226 | - (void)performOperations
|
227 | {
|
228 | if (_wantRunUpdates) {
|
229 | [REANode runPropUpdates:_updateContext];
|
230 | }
|
231 | if (_operationsInBatch.count != 0) {
|
232 | NSMutableArray<REANativeAnimationOp> *copiedOperationsQueue = _operationsInBatch;
|
233 | _operationsInBatch = [NSMutableArray new];
|
234 |
|
235 | BOOL trySynchronously = _tryRunBatchUpdatesSynchronously;
|
236 | _tryRunBatchUpdatesSynchronously = NO;
|
237 |
|
238 | __weak typeof(self) weakSelf = self;
|
239 | dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
|
240 | RCTExecuteOnUIManagerQueue(^{
|
241 | __typeof__(self) strongSelf = weakSelf;
|
242 | if (strongSelf == nil) {
|
243 | return;
|
244 | }
|
245 | BOOL canUpdateSynchronously = trySynchronously && ![strongSelf.uiManager hasEnqueuedUICommands];
|
246 |
|
247 | if (!canUpdateSynchronously) {
|
248 | dispatch_semaphore_signal(semaphore);
|
249 | }
|
250 |
|
251 | for (int i = 0; i < copiedOperationsQueue.count; i++) {
|
252 | copiedOperationsQueue[i](strongSelf.uiManager);
|
253 | }
|
254 |
|
255 | if (canUpdateSynchronously) {
|
256 | [strongSelf.uiManager runSyncUIUpdatesWithObserver:self];
|
257 | dispatch_semaphore_signal(semaphore);
|
258 | }
|
259 | //In case canUpdateSynchronously=true we still have to send uiManagerWillPerformMounting event
|
260 | //to observers because some components (e.g. TextInput) update their UIViews only on that event.
|
261 | [strongSelf.uiManager setNeedsLayout];
|
262 | });
|
263 | if (trySynchronously) {
|
264 | dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
|
265 | }
|
266 |
|
267 | if (_mounting) {
|
268 | _mounting();
|
269 | _mounting = nil;
|
270 | }
|
271 | }
|
272 | _wantRunUpdates = NO;
|
273 | }
|
274 |
|
275 | - (void)enqueueUpdateViewOnNativeThread:(nonnull NSNumber *)reactTag
|
276 | viewName:(NSString *) viewName
|
277 | nativeProps:(NSMutableDictionary *)nativeProps
|
278 | trySynchronously:(BOOL)trySync {
|
279 | if (trySync) {
|
280 | _tryRunBatchUpdatesSynchronously = YES;
|
281 | }
|
282 | [_operationsInBatch addObject:^(RCTUIManager *uiManager) {
|
283 | [uiManager updateView:reactTag viewName:viewName props:nativeProps];
|
284 | }];
|
285 | }
|
286 |
|
287 | - (void)getValue:(REANodeID)nodeID
|
288 | callback:(RCTResponseSenderBlock)callback
|
289 | {
|
290 | id val = _nodes[nodeID].value;
|
291 | if (val) {
|
292 | callback(@[val]);
|
293 | } else {
|
294 | // NULL is not an object and it's not possible to pass it as callback's argument
|
295 | callback(@[[NSNull null]]);
|
296 | }
|
297 | }
|
298 |
|
299 | #pragma mark -- Graph
|
300 |
|
301 | - (void)createNode:(REANodeID)nodeID
|
302 | config:(NSDictionary<NSString *, id> *)config
|
303 | {
|
304 | static NSDictionary *map;
|
305 | static dispatch_once_t mapToken;
|
306 | dispatch_once(&mapToken, ^{
|
307 | map = @{@"props": [REAPropsNode class],
|
308 | @"style": [REAStyleNode class],
|
309 | @"transform": [REATransformNode class],
|
310 | @"value": [REAValueNode class],
|
311 | @"block": [REABlockNode class],
|
312 | @"cond": [REACondNode class],
|
313 | @"op": [REAOperatorNode class],
|
314 | @"set": [REASetNode class],
|
315 | @"debug": [READebugNode class],
|
316 | @"clock": [REAClockNode class],
|
317 | @"clockStart": [REAClockStartNode class],
|
318 | @"clockStop": [REAClockStopNode class],
|
319 | @"clockTest": [REAClockTestNode class],
|
320 | @"call": [REAJSCallNode class],
|
321 | @"bezier": [REABezierNode class],
|
322 | @"event": [REAEventNode class],
|
323 | @"always": [REAAlwaysNode class],
|
324 | @"concat": [REAConcatNode class],
|
325 | @"param": [REAParamNode class],
|
326 | @"func": [REAFunctionNode class],
|
327 | @"callfunc": [REACallFuncNode class]
|
328 | // @"listener": nil,
|
329 | };
|
330 | });
|
331 |
|
332 | NSString *nodeType = [RCTConvert NSString:config[@"type"]];
|
333 |
|
334 | Class nodeClass = map[nodeType];
|
335 | if (!nodeClass) {
|
336 | RCTLogError(@"Animated node type %@ not supported natively", nodeType);
|
337 | return;
|
338 | }
|
339 |
|
340 | REANode *node = [[nodeClass alloc] initWithID:nodeID config:config];
|
341 | node.nodesManager = self;
|
342 | node.updateContext = _updateContext;
|
343 | _nodes[nodeID] = node;
|
344 | }
|
345 |
|
346 | - (void)dropNode:(REANodeID)nodeID
|
347 | {
|
348 | REANode *node = _nodes[nodeID];
|
349 | if (node) {
|
350 | [node onDrop];
|
351 | [_nodes removeObjectForKey:nodeID];
|
352 | }
|
353 | }
|
354 |
|
355 | - (void)connectNodes:(nonnull NSNumber *)parentID childID:(nonnull REANodeID)childID
|
356 | {
|
357 | RCTAssertParam(parentID);
|
358 | RCTAssertParam(childID);
|
359 |
|
360 | REANode *parentNode = _nodes[parentID];
|
361 | REANode *childNode = _nodes[childID];
|
362 |
|
363 | RCTAssertParam(childNode);
|
364 |
|
365 | [parentNode addChild:childNode];
|
366 | }
|
367 |
|
368 | - (void)disconnectNodes:(REANodeID)parentID childID:(REANodeID)childID
|
369 | {
|
370 | RCTAssertParam(parentID);
|
371 | RCTAssertParam(childID);
|
372 |
|
373 | REANode *parentNode = _nodes[parentID];
|
374 | REANode *childNode = _nodes[childID];
|
375 |
|
376 | RCTAssertParam(childNode);
|
377 |
|
378 | [parentNode removeChild:childNode];
|
379 | }
|
380 |
|
381 | - (void)connectNodeToView:(REANodeID)nodeID
|
382 | viewTag:(NSNumber *)viewTag
|
383 | viewName:(NSString *)viewName
|
384 | {
|
385 | RCTAssertParam(nodeID);
|
386 | REANode *node = _nodes[nodeID];
|
387 | RCTAssertParam(node);
|
388 |
|
389 | if ([node isKindOfClass:[REAPropsNode class]]) {
|
390 | [(REAPropsNode *)node connectToView:viewTag viewName:viewName];
|
391 | }
|
392 | }
|
393 |
|
394 | - (void)disconnectNodeFromView:(REANodeID)nodeID
|
395 | viewTag:(NSNumber *)viewTag
|
396 | {
|
397 | RCTAssertParam(nodeID);
|
398 | REANode *node = _nodes[nodeID];
|
399 | RCTAssertParam(node);
|
400 |
|
401 | if ([node isKindOfClass:[REAPropsNode class]]) {
|
402 | [(REAPropsNode *)node disconnectFromView:viewTag];
|
403 | }
|
404 | }
|
405 |
|
406 | - (void)attachEvent:(NSNumber *)viewTag
|
407 | eventName:(NSString *)eventName
|
408 | eventNodeID:(REANodeID)eventNodeID
|
409 | {
|
410 | RCTAssertParam(eventNodeID);
|
411 | REANode *eventNode = _nodes[eventNodeID];
|
412 | RCTAssert([eventNode isKindOfClass:[REAEventNode class]], @"Event node is of an invalid type");
|
413 |
|
414 | NSString *key = [NSString stringWithFormat:@"%@%@",
|
415 | viewTag,
|
416 | RCTNormalizeInputEventName(eventName)];
|
417 | RCTAssert([_eventMapping objectForKey:key] == nil, @"Event handler already set for the given view and event type");
|
418 | [_eventMapping setObject:eventNode forKey:key];
|
419 | }
|
420 |
|
421 | - (void)detachEvent:(NSNumber *)viewTag
|
422 | eventName:(NSString *)eventName
|
423 | eventNodeID:(REANodeID)eventNodeID
|
424 | {
|
425 | NSString *key = [NSString stringWithFormat:@"%@%@",
|
426 | viewTag,
|
427 | RCTNormalizeInputEventName(eventName)];
|
428 | [_eventMapping removeObjectForKey:key];
|
429 | }
|
430 |
|
431 | - (void)processEvent:(id<RCTEvent>)event
|
432 | {
|
433 | NSString *key = [NSString stringWithFormat:@"%@%@",
|
434 | event.viewTag,
|
435 | RCTNormalizeInputEventName(event.eventName)];
|
436 | REAEventNode *eventNode = [_eventMapping objectForKey:key];
|
437 | [eventNode processEvent:event];
|
438 | }
|
439 |
|
440 | - (void)processDirectEvent:(id<RCTEvent>)event
|
441 | {
|
442 | _processingDirectEvent = YES;
|
443 | [self processEvent:event];
|
444 | [self performOperations];
|
445 | _processingDirectEvent = NO;
|
446 | }
|
447 |
|
448 | - (BOOL)isDirectEvent:(id<RCTEvent>)event
|
449 | {
|
450 | static NSArray<NSString *> *directEventNames;
|
451 | static dispatch_once_t directEventNamesToken;
|
452 | dispatch_once(&directEventNamesToken, ^{
|
453 | directEventNames = @[
|
454 | @"topContentSizeChange",
|
455 | @"topMomentumScrollBegin",
|
456 | @"topMomentumScrollEnd",
|
457 | @"topScroll",
|
458 | @"topScrollBeginDrag",
|
459 | @"topScrollEndDrag"
|
460 | ];
|
461 | });
|
462 |
|
463 | return [directEventNames containsObject:RCTNormalizeInputEventName(event.eventName)];
|
464 | }
|
465 |
|
466 | - (void)dispatchEvent:(id<RCTEvent>)event
|
467 | {
|
468 | NSString *key = [NSString stringWithFormat:@"%@%@",
|
469 | event.viewTag,
|
470 | RCTNormalizeInputEventName(event.eventName)];
|
471 |
|
472 | NSString *eventHash = [NSString stringWithFormat:@"%@%@",
|
473 | event.viewTag,
|
474 | event.eventName];
|
475 |
|
476 | if (_eventHandler != nil) {
|
477 | __weak REAEventHandler eventHandler = _eventHandler;
|
478 | __weak typeof(self) weakSelf = self;
|
479 | RCTExecuteOnMainQueue(^void(){
|
480 | __typeof__(self) strongSelf = weakSelf;
|
481 | if (strongSelf == nil) {
|
482 | return;
|
483 | }
|
484 | eventHandler(eventHash, event);
|
485 | if ([strongSelf isDirectEvent:event]) {
|
486 | [strongSelf performOperations];
|
487 | }
|
488 | });
|
489 | }
|
490 |
|
491 | REANode *eventNode = [_eventMapping objectForKey:key];
|
492 |
|
493 | if (eventNode != nil) {
|
494 | if ([self isDirectEvent:event]) {
|
495 | // Bypass the event queue/animation frames and process scroll events
|
496 | // immediately to avoid getting out of sync with the scroll position
|
497 | [self processDirectEvent:event];
|
498 | } else {
|
499 | // enqueue node to be processed
|
500 | [_eventQueue addObject:event];
|
501 | [self startUpdatingOnAnimationFrame];
|
502 | }
|
503 | }
|
504 | }
|
505 |
|
506 | - (void)configureProps:(NSSet<NSString *> *)nativeProps
|
507 | uiProps:(NSSet<NSString *> *)uiProps
|
508 | {
|
509 | _uiProps = uiProps;
|
510 | _nativeProps = nativeProps;
|
511 | }
|
512 |
|
513 | - (void)setValueForNodeID:(nonnull NSNumber *)nodeID value:(nonnull NSNumber *)newValue
|
514 | {
|
515 | RCTAssertParam(nodeID);
|
516 |
|
517 | REANode *node = _nodes[nodeID];
|
518 |
|
519 | REAValueNode *valueNode = (REAValueNode *)node;
|
520 | [valueNode setValue:newValue];
|
521 | }
|
522 |
|
523 | - (void)updateProps:(nonnull NSDictionary *)props
|
524 | ofViewWithTag:(nonnull NSNumber *)viewTag
|
525 | withName:(nonnull NSString *)viewName
|
526 | {
|
527 | // TODO: refactor PropsNode to also use this function
|
528 | NSMutableDictionary *uiProps = [NSMutableDictionary new];
|
529 | NSMutableDictionary *nativeProps = [NSMutableDictionary new];
|
530 | NSMutableDictionary *jsProps = [NSMutableDictionary new];
|
531 |
|
532 | void (^addBlock)(NSString *key, id obj, BOOL * stop) = ^(NSString *key, id obj, BOOL * stop){
|
533 | if ([self.uiProps containsObject:key]) {
|
534 | uiProps[key] = obj;
|
535 | } else if ([self.nativeProps containsObject:key]) {
|
536 | nativeProps[key] = obj;
|
537 | } else {
|
538 | jsProps[key] = obj;
|
539 | }
|
540 | };
|
541 |
|
542 | [props enumerateKeysAndObjectsUsingBlock:addBlock];
|
543 |
|
544 | if (uiProps.count > 0) {
|
545 | [self.uiManager
|
546 | synchronouslyUpdateViewOnUIThread:viewTag
|
547 | viewName:viewName
|
548 | props:uiProps];
|
549 | }
|
550 | if (nativeProps.count > 0) {
|
551 | [self enqueueUpdateViewOnNativeThread:viewTag viewName:viewName nativeProps:nativeProps trySynchronously:YES];
|
552 | }
|
553 | if (jsProps.count > 0) {
|
554 | [self.reanimatedModule sendEventWithName:@"onReanimatedPropsChange"
|
555 | body:@{@"viewTag": viewTag, @"props": jsProps }];
|
556 | }
|
557 | }
|
558 |
|
559 | - (NSString*)obtainProp:(nonnull NSNumber *)viewTag
|
560 | propName:(nonnull NSString *)propName
|
561 | {
|
562 | UIView* view = [self.uiManager viewForReactTag:viewTag];
|
563 |
|
564 | NSString* result = [NSString stringWithFormat:@"error: unknown propName %@, currently supported: opacity, zIndex", propName];
|
565 |
|
566 | if ([propName isEqualToString:@"opacity"]) {
|
567 | CGFloat alpha = view.alpha;
|
568 | result = [@(alpha) stringValue];
|
569 | } else if ([propName isEqualToString:@"zIndex"]) {
|
570 | NSInteger zIndex = view.reactZIndex;
|
571 | result = [@(zIndex) stringValue];
|
572 | }
|
573 |
|
574 | return result;
|
575 | }
|
576 |
|
577 | @end
|