Refactor Layout Plugin

Summary:
This diff moves the iOS descriptors and other layout helpers into its own buck target. Earlier everything was part of the FlipperKitLayoutPlugin target. When kcui, tried to add macos descriptors, we realised that FlipperKitLayoutPlugin doesn't have macos platform in it and thus he had to put #if TARGET_OS_SIMULATOR check in iOS descriptors and at some other places.

In order to make the integration easier I decided to split the Layout Plugin in following way.

```
FlipperKitLayoutPlugin
  -> FlipperKitLayoutIOSDescriptors (platform set as IOS)
  -> FlipperKitLayoutHelpers (used in iOS Descriptors and usefule for Macos too)
  -> FlipperKitLayoutMacOSDescriptors(coming soon by kcui)(platform set as MacOS)

```

Reviewed By: kcui

Differential Revision: D27127587

fbshipit-source-id: f108ef81914297e93c886fe53dabf0a4bf11ebc1
This commit is contained in:
Pritesh Nandgaonkar
2021-03-25 15:59:15 -07:00
committed by Facebook GitHub Bot
parent 8c85da5bc8
commit 790f23301e
61 changed files with 133 additions and 80 deletions

View File

@@ -0,0 +1,22 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@class SKNodeDescriptor;
@protocol SKDescriptorMapperProtocol<NSObject>
- (instancetype)initWithDefaults;
- (SKNodeDescriptor*)descriptorForClass:(Class)cls;
- (void)registerDescriptor:(SKNodeDescriptor*)descriptor forClass:(Class)cls;
- (NSArray<SKNodeDescriptor*>*)allDescriptors;
@end

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@protocol SKInvalidationDelegate
- (void)invalidateNode:(id<NSObject>)node;
- (void)updateNodeReference:(id<NSObject>)node;
@end
@interface SKInvalidation : NSObject
+ (instancetype)sharedInstance;
+ (void)enableInvalidations;
@property(nonatomic, weak) id<SKInvalidationDelegate> delegate;
@end

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if FB_SONARKIT_ENABLED
#import <UIKit/UIKit.h>
#import "SKInvalidation.h"
#import "UICollectionView+SKInvalidation.h"
#import "UIView+SKInvalidation.h"
@implementation SKInvalidation
+ (instancetype)sharedInstance {
static SKInvalidation* sInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sInstance = [SKInvalidation new];
});
return sInstance;
}
+ (void)enableInvalidations {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[UIView enableInvalidation];
[UICollectionView enableInvalidations];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(windowDidBecomeVisible:)
name:UIWindowDidBecomeVisibleNotification
object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(windowDidBecomeHidden:)
name:UIWindowDidBecomeHiddenNotification
object:nil];
});
}
+ (void)windowDidBecomeVisible:(NSNotification*)notification {
[[SKInvalidation sharedInstance].delegate
invalidateNode:[notification.object nextResponder]];
}
+ (void)windowDidBecomeHidden:(NSNotification*)notification {
[[SKInvalidation sharedInstance].delegate
invalidateNode:[notification.object nextResponder]];
}
@end
#endif

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
@interface SKNamed<__covariant T> : NSObject
+ (instancetype)newWithName:(NSString*)name withValue:(T)value;
@property(nonatomic, readonly) NSString* name;
@property(nonatomic, readonly) T value;
@end

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if FB_SONARKIT_ENABLED
#import "SKNamed.h"
@implementation SKNamed
+ (instancetype)newWithName:(NSString*)name withValue:(id)value {
return [[SKNamed alloc] initWithName:name withValue:value];
}
- (instancetype)initWithName:(NSString*)name withValue:(id)value {
if (self = [super init]) {
_name = name;
_value = value;
}
return self;
}
- (NSString*)description {
return [NSString stringWithFormat:@"%@: %@", _name, _value];
}
@end
#endif

View File

@@ -0,0 +1,133 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <FlipperKitLayoutHelpers/FlipperKitLayoutDescriptorMapperProtocol.h>
#import "SKNamed.h"
#import "SKTouch.h"
typedef void (^SKNodeUpdateData)(id value);
/**
A SKNodeDescriptor is an object which know how to expose an Object of type T
to SonarKitLayoutPlugin. This class is the extension point for
SonarKitLayoutPlugin and is how custom objects or data can be exposed to Sonar.
*/
@interface SKNodeDescriptor<__covariant T> : NSObject
/**
If the descriptor class is dependent on some set-up, use this.
This is invoked once Sonar connects.
*/
- (void)setUp;
/**
Initializes the node-descriptor with a SKDescriptorMapper which contains
mappings between Class -> SKNodeDescriptor<Class>.
*/
- (instancetype)initWithDescriptorMapper:(id<SKDescriptorMapperProtocol>)mapper;
/**
Gets the node-descriptor registered for a specific class.
*/
- (SKNodeDescriptor*)descriptorForClass:(Class)cls;
/**
A globally unique ID used to identify a node in the hierarchy. This is used
in the communication between SonarKitLayoutPlugin and the Sonar desktop
application in order to identify nodes.
*/
- (NSString*)identifierForNode:(T)node;
/**
When the setData command is received from Flipper to change a node's data,
an "invalidateWithData" command is sent back to signal that the node has
changed. However sometimes you may want to invalidate some other node,
not the node that had its data actually modified; usually some ancestor.
This method allows you to substitute another node's identifier.
If you do not override it, the default behavior is to simply return
the node's identifier.
*/
- (NSString*)identifierForInvalidation:(T)node;
/**
The name used to identify this node in the Sonar desktop application. This is
what will be visible in the hierarchy.
*/
- (NSString*)nameForNode:(T)node;
/**
The number of children this node exposes in the layout hierarchy.
*/
- (NSUInteger)childCountForNode:(T)node;
/**
Get the child for a specific node at a specified index.
*/
- (id)childForNode:(T)node atIndex:(NSUInteger)index;
/**
Get the data to show for this node in the sidebar of the Sonar application. The
objects will be shown in order by SKNamed.name as their header.
*/
- (NSArray<SKNamed<NSDictionary*>*>*)dataForNode:(T)node;
/**
Get the extra info to pass it back to Sonar application without showing them in
the sidebar.
*/
- (NSArray<SKNamed<NSDictionary*>*>*)extraInfoForNode:(T)node;
/**
Get the attributes for this node. Attributes will be showed in the Sonar
application right next to the name of the node.
*/
- (NSArray<SKNamed<NSString*>*>*)attributesForNode:(T)node;
/**
A mapping of the path for a specific value, and a block responsible for
updating its corresponding value for a specific node.
The paths (string) is dependent on what `dataForNode` returns (e.g
"SKNodeDescriptor.name").
*/
- (NSDictionary<NSString*, SKNodeUpdateData>*)dataMutationsForNode:(T)node;
/**
This is used in order to highlight any specific node which is currently
selected in the Sonar application. The plugin automatically takes care of
de-selecting the previously highlighted node.
*/
- (void)setHighlighted:(BOOL)highlighted forNode:(T)node;
/**
Perform hit testing on the given node. Either continue the search in
one of the children of the node, or finish the hit testing on this
node.
*/
- (void)hitTest:(SKTouch*)point forNode:(T)node;
/**
Invalidate a specific node. This is called once a node is removed or added
from or to the layout hierarchy.
*/
- (void)invalidateNode:(T)node;
/**
The decoration for this node. Valid values are defined in the Sonar
applictation.
*/
- (NSString*)decorationForNode:(T)node;
/**
Whether the node matches the given query.
Used for layout search.
*/
- (BOOL)matchesQuery:(NSString*)query forNode:(T)node;
@end

View File

@@ -0,0 +1,103 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if FB_SONARKIT_ENABLED
#import "SKNodeDescriptor.h"
#import <FlipperKitLayoutTextSearchable/FKTextSearchable.h>
@implementation SKNodeDescriptor {
id<SKDescriptorMapperProtocol> _mapper;
}
- (void)setUp {
}
- (instancetype)initWithDescriptorMapper:
(id<SKDescriptorMapperProtocol>)mapper {
if (self = [super init]) {
_mapper = mapper;
}
return self;
}
- (SKNodeDescriptor*)descriptorForClass:(Class)cls {
return [_mapper descriptorForClass:cls];
}
- (NSString*)identifierForNode:(id)node {
@throw [NSString
stringWithFormat:@"need to implement %@", NSStringFromSelector(_cmd)];
}
- (NSString*)identifierForInvalidation:(id)node {
return [self identifierForNode:node];
}
- (NSString*)nameForNode:(id)node {
return NSStringFromClass([node class]);
}
- (NSUInteger)childCountForNode:(id)node {
@throw [NSString
stringWithFormat:@"need to implement %@", NSStringFromSelector(_cmd)];
}
- (id)childForNode:(id)node atIndex:(NSUInteger)index {
@throw [NSString
stringWithFormat:@"need to implement %@", NSStringFromSelector(_cmd)];
}
- (NSDictionary<NSString*, SKNodeUpdateData>*)dataMutationsForNode:(id)node {
return @{};
}
- (NSArray<SKNamed<NSDictionary*>*>*)dataForNode:(id)node {
return @[];
}
- (NSArray<SKNamed<NSDictionary*>*>*)extraInfoForNode:(id)node {
return @[];
}
- (NSArray<SKNamed<NSString*>*>*)attributesForNode:(id)node {
return @[];
}
- (void)setHighlighted:(BOOL)highlighted forNode:(id)node {
}
- (void)hitTest:(SKTouch*)point forNode:(id)node {
}
- (void)invalidateNode:(id)node {
}
- (NSString*)decorationForNode:(id)node {
return @"";
}
- (BOOL)matchesQuery:(NSString*)query forNode:(id)node {
NSString* name = [self nameForNode:node];
NSString* text = nil;
if ([node conformsToProtocol:@protocol(FKTextSearchable)]) {
text = [node searchableText];
}
return [self string:name contains:query] ||
[self string:[self identifierForNode:node] contains:query] ||
[self string:text contains:query];
}
- (BOOL)string:(NSString*)string contains:(NSString*)substring {
return string != nil && substring != nil &&
[string rangeOfString:substring options:NSCaseInsensitiveSearch]
.location != NSNotFound;
}
@end
#endif

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
@protocol SKSonarValueCoder
+ (instancetype)fromSonarValue:(id)sonarValue;
- (NSDictionary<NSString*, id<NSObject>>*)sonarValue;
@end
class SKObject {
public:
SKObject(CGRect rect);
SKObject(CGSize size);
SKObject(CGPoint point);
SKObject(UIEdgeInsets insets);
SKObject(CGAffineTransform transform);
SKObject(id<SKSonarValueCoder> value);
SKObject(id value);
operator id<NSObject>() const noexcept {
return _actual ?: [NSNull null];
}
protected:
id<NSObject> _actual;
};
class SKMutableObject : public SKObject {
public:
SKMutableObject(CGRect rect) : SKObject(rect) {}
SKMutableObject(CGSize size) : SKObject(size){};
SKMutableObject(CGPoint point) : SKObject(point){};
SKMutableObject(UIEdgeInsets insets) : SKObject(insets){};
SKMutableObject(CGAffineTransform transform) : SKObject(transform){};
SKMutableObject(id<SKSonarValueCoder> value) : SKObject(value){};
SKMutableObject(id value) : SKObject(value){};
operator id<NSObject>() {
convertToMutable();
return _actual;
}
protected:
BOOL _convertedToMutable = NO;
void convertToMutable();
};

View File

@@ -0,0 +1,112 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if FB_SONARKIT_ENABLED
#import "SKObject.h"
SKObject::SKObject(CGRect rect) {
_actual = @{@"origin" : SKObject(rect.origin), @"size" : SKObject(rect.size)};
}
SKObject::SKObject(CGSize size) {
_actual = @{@"height" : @(size.height), @"width" : @(size.width)};
}
SKObject::SKObject(CGPoint point) {
_actual = @{@"x" : @(point.x), @"y" : @(point.y)};
}
SKObject::SKObject(UIEdgeInsets insets) {
_actual = @{
@"top" : @(insets.top),
@"bottom" : @(insets.bottom),
@"left" : @(insets.left),
@"right" : @(insets.right),
};
}
SKObject::SKObject(CGAffineTransform transform) {
_actual = @{
@"a" : @(transform.a),
@"b" : @(transform.b),
@"c" : @(transform.c),
@"d" : @(transform.d),
@"tx" : @(transform.tx),
@"ty" : @(transform.ty),
};
}
SKObject::SKObject(id<SKSonarValueCoder> value) : _actual([value sonarValue]) {}
SKObject::SKObject(id value) : _actual(value) {}
static NSString* _objectType(id<NSObject> object) {
if ([object isKindOfClass:[NSDictionary class]]) {
return (NSString*)((NSDictionary*)object)[@"__type__"];
}
return nil;
}
static id<NSObject> _objectValue(id<NSObject> object) {
if ([object isKindOfClass:[NSDictionary class]]) {
return ((NSDictionary*)object)[@"value"];
}
return object;
}
static NSDictionary<NSString*, id<NSObject>>* _SKValue(
id<NSObject> object,
BOOL isMutable) {
NSString* type = _objectType(object);
id<NSObject> value = _objectValue(object);
return @{
@"__type__" : (type != nil ? type : @"auto"),
@"__mutable__" : @(isMutable),
@"value" : (value != nil ? value : [NSNull null]),
};
}
static NSDictionary* _SKMutable(
const NSDictionary<NSString*, id<NSObject>>* skObject) {
NSMutableDictionary* mutableObject = [NSMutableDictionary new];
for (NSString* key : skObject) {
id<NSObject> value = skObject[key];
if (_objectType(value) != nil) {
mutableObject[key] = _SKValue(value, YES);
} else if ([value isKindOfClass:[NSDictionary class]]) {
auto objectValue = (NSDictionary<NSString*, id<NSObject>>*)value;
mutableObject[key] = _SKMutable(objectValue);
} else {
mutableObject[key] = _SKValue(value, YES);
}
}
return mutableObject;
}
void SKMutableObject::convertToMutable() {
if (_convertedToMutable) {
return;
}
if (_objectType(_actual) == nil &&
[_actual isKindOfClass:[NSDictionary class]]) {
auto object = (const NSDictionary<NSString*, id<NSObject>>*)_actual;
_actual = _SKMutable(object);
} else {
_actual = _SKValue(_actual, YES);
}
_convertedToMutable = YES;
}
#endif

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#ifndef SKSearchResultNode_h
#define SKSearchResultNode_h
#import <Foundation/Foundation.h>
@interface SKSearchResultNode : NSObject
@property(nonatomic, copy, readonly) NSString* nodeId;
- (instancetype)initWithNode:(NSString*)nodeId
asMatch:(BOOL)isMatch
withElement:(NSDictionary*)element
andChildren:(NSArray<SKSearchResultNode*>*)children;
- (NSDictionary*)toNSDictionary;
@end
#endif /* SKSearchResultNode_h */

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "SKSearchResultNode.h"
@implementation SKSearchResultNode {
NSString* _nodeId;
BOOL _isMatch;
NSDictionary* _element;
NSArray<SKSearchResultNode*>* _children;
}
- (instancetype)initWithNode:(NSString*)nodeId
asMatch:(BOOL)isMatch
withElement:(NSDictionary*)element
andChildren:(NSArray<SKSearchResultNode*>*)children {
self = [super init];
if (self) {
_nodeId = nodeId;
_isMatch = isMatch;
_element = element;
_children = children;
}
return self;
}
- (NSDictionary*)toNSDictionary {
if (_element == nil) {
return nil;
}
NSMutableArray<NSDictionary*>* childArray;
if (_children) {
childArray = [NSMutableArray new];
for (SKSearchResultNode* child in _children) {
NSDictionary* childDict = [child toNSDictionary];
if (childDict) {
[childArray addObject:childDict];
}
}
} else {
childArray = nil;
}
return @{
@"id" : _nodeId,
@"isMatch" : @(_isMatch),
@"element" : _element,
@"children" : childArray ?: [NSNull null]
};
}
@end

View File

@@ -0,0 +1,22 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
typedef void (^SKTapReceiver)(CGPoint touchPoint);
@protocol SKTapListener
@property(nonatomic, readonly) BOOL isMounted;
- (void)mountWithFrame:(CGRect)frame;
- (void)unmount;
- (void)listenForTapWithBlock:(SKTapReceiver)receiver;
@end

View File

@@ -0,0 +1,13 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "SKTapListener.h"
@interface SKTapListenerImpl
: NSObject<SKTapListener, UIGestureRecognizerDelegate>
@end

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if FB_SONARKIT_ENABLED
#import "SKTapListenerImpl.h"
#import "SKHiddenWindow.h"
#import <FlipperKitHighlightOverlay/SKHighlightOverlay.h>
@implementation SKTapListenerImpl {
NSMutableArray<SKTapReceiver>* _receiversWaitingForInput;
UITapGestureRecognizer* _gestureRecognizer;
SKHiddenWindow* _overlayWindow;
}
@synthesize isMounted = _isMounted;
- (instancetype)init {
if (self = [super init]) {
_receiversWaitingForInput = [NSMutableArray new];
_gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
action:nil];
_gestureRecognizer.delegate = self;
_isMounted = NO;
_overlayWindow = [SKHiddenWindow new];
_overlayWindow.hidden = YES;
_overlayWindow.windowLevel = UIWindowLevelAlert;
_overlayWindow.backgroundColor = [SKHighlightOverlay overlayColor];
[_overlayWindow addGestureRecognizer:_gestureRecognizer];
}
return self;
}
- (void)mountWithFrame:(CGRect)frame {
if (_isMounted) {
return;
}
[_overlayWindow setFrame:frame];
[_overlayWindow makeKeyAndVisible];
_overlayWindow.hidden = NO;
[[UIApplication sharedApplication].delegate.window addSubview:_overlayWindow];
_isMounted = YES;
}
- (void)unmount {
if (!_isMounted) {
return;
}
[_receiversWaitingForInput removeAllObjects];
[_overlayWindow removeFromSuperview];
_overlayWindow.hidden = YES;
_isMounted = NO;
}
- (void)listenForTapWithBlock:(SKTapReceiver)receiver {
[_receiversWaitingForInput addObject:receiver];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
shouldReceiveTouch:(UITouch*)touch {
if ([_receiversWaitingForInput count] == 0) {
return YES;
}
CGPoint touchPoint = [touch locationInView:_overlayWindow];
for (SKTapReceiver recv in _receiversWaitingForInput) {
recv(touchPoint);
}
[_receiversWaitingForInput removeAllObjects];
return NO;
}
@end
#endif

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <FlipperKitLayoutHelpers/FlipperKitLayoutDescriptorMapperProtocol.h>
#import <UIKit/UIKit.h>
typedef void (^SKTouchFinishDelegate)(id<NSObject> currentNode);
typedef void (^SKProcessFinishDelegate)(NSDictionary* tree);
@interface SKTouch : NSObject
- (instancetype)initWithTouchPoint:(CGPoint)touchPoint
withRootNode:(id<NSObject>)node
withDescriptorMapper:(id<SKDescriptorMapperProtocol>)mapper
finishWithBlock:(SKTouchFinishDelegate)d;
- (void)continueWithChildIndex:(NSUInteger)childIndex
withOffset:(CGPoint)offset;
- (void)finish;
- (void)retrieveSelectTree:(SKProcessFinishDelegate)callback;
- (BOOL)containedIn:(CGRect)bounds;
@end

View File

@@ -0,0 +1,82 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if FB_SONARKIT_ENABLED
#import "SKTouch.h"
#import "SKNodeDescriptor.h"
@implementation SKTouch {
SKTouchFinishDelegate _onFinish;
CGPoint _currentTouchPoint;
id<SKDescriptorMapperProtocol> _descriptorMapper;
NSMutableArray<id<NSObject>>* _nodeStack;
NSMutableArray<NSMutableDictionary*>* _treeStack;
}
- (instancetype)initWithTouchPoint:(CGPoint)touchPoint
withRootNode:(id<NSObject>)node
withDescriptorMapper:(id<SKDescriptorMapperProtocol>)mapper
finishWithBlock:(SKTouchFinishDelegate)finishBlock {
if (self = [super init]) {
_onFinish = finishBlock;
_currentTouchPoint = touchPoint;
_descriptorMapper = mapper;
_nodeStack = [NSMutableArray new];
[_nodeStack addObject:node];
_treeStack = [NSMutableArray new];
[_treeStack addObject:[[NSMutableDictionary alloc] init]];
}
return self;
}
- (void)continueWithChildIndex:(NSUInteger)childIndex
withOffset:(CGPoint)offset {
_currentTouchPoint.x -= offset.x;
_currentTouchPoint.y -= offset.y;
id<NSObject> currentNode = _nodeStack.lastObject;
SKNodeDescriptor* descriptor =
[_descriptorMapper descriptorForClass:[currentNode class]];
id<NSObject> nextNode = [descriptor childForNode:currentNode
atIndex:childIndex];
[_nodeStack addObject:nextNode];
[_treeStack addObject:[[NSMutableDictionary alloc] init]];
descriptor = [_descriptorMapper descriptorForClass:[nextNode class]];
NSString* currentId = [descriptor identifierForNode:nextNode];
[descriptor hitTest:self forNode:nextNode];
// After finish this component
_currentTouchPoint.x += offset.x;
_currentTouchPoint.y += offset.y;
[_nodeStack removeLastObject];
NSDictionary* currentDict = _treeStack.lastObject;
[_treeStack removeLastObject];
[_treeStack.lastObject setObject:currentDict forKey:currentId];
}
- (void)finish {
_onFinish(_nodeStack.lastObject);
}
- (void)retrieveSelectTree:(SKProcessFinishDelegate)callback {
callback(_treeStack.lastObject);
[_treeStack removeAllObjects];
}
- (BOOL)containedIn:(CGRect)bounds {
return CGRectContainsPoint(bounds, _currentTouchPoint);
}
@end
#endif

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <FlipperKit/SKMacros.h>
FB_LINK_REQUIRE_CATEGORY(UICollectionView_SKInvalidation)
@interface UICollectionView (SKInvalidation)
+ (void)enableInvalidations;
@end

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if FB_SONARKIT_ENABLED
#import "UICollectionView+SKInvalidation.h"
#import "SKInvalidation.h"
#import "SKSwizzle.h"
FB_LINKABLE(UICollectionView_SKInvalidation)
@implementation UICollectionView (SKInvalidation)
+ (void)enableInvalidations {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
swizzleMethods(
[self class],
@selector(cellForItemAtIndexPath:),
@selector(swizzle_cellForItemAtIndexPath:));
});
}
- (UICollectionViewCell*)swizzle_cellForItemAtIndexPath:
(NSIndexPath*)indexPath {
dispatch_async(dispatch_get_main_queue(), ^{
[[SKInvalidation sharedInstance].delegate invalidateNode:self];
});
return [self swizzle_cellForItemAtIndexPath:indexPath];
}
@end
#endif

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <FlipperKit/SKMacros.h>
#import "SKObject.h"
FB_LINK_REQUIRE_CATEGORY(UIColor_SonarValueCoder)
@interface UIColor (SonarValueCoder)<SKSonarValueCoder>
@end

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if FB_SONARKIT_ENABLED
#import "UIColor+SKSonarValueCoder.h"
FB_LINKABLE(UIColor_SonarValueCoder)
@implementation UIColor (SonarValueCoder)
+ (instancetype)fromSonarValue:(NSNumber*)sonarValue {
NSUInteger intColor = [sonarValue integerValue];
CGFloat r, g, b, a;
b = CGFloat(intColor & 0xFF) / 255;
g = CGFloat((intColor >> 8) & 0xFF) / 255;
r = CGFloat((intColor >> 16) & 0xFF) / 255;
a = CGFloat((intColor >> 24) & 0xFF) / 255;
return [[UIColor alloc] initWithRed:r green:g blue:b alpha:a];
}
- (NSDictionary<NSString*, id<NSObject>>*)sonarValue {
CGColorSpaceRef colorSpace = CGColorGetColorSpace([self CGColor]);
CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace);
NSUInteger red, green, blue, alpha;
switch (colorSpaceModel) {
case kCGColorSpaceModelUnknown:
case kCGColorSpaceModelRGB: {
CGFloat r, g, b, a;
[self getRed:&r green:&g blue:&b alpha:&a];
red = (NSUInteger)(r * 255) & 0xFF;
green = (NSUInteger)(g * 255) & 0xFF;
blue = (NSUInteger)(b * 255) & 0xFF;
alpha = (NSUInteger)(a * 255) & 0xFF;
} break;
case kCGColorSpaceModelMonochrome: {
CGFloat a, w;
[self getWhite:&w alpha:&a];
red = green = blue = (NSUInteger)(w * 255) & 0xFF;
alpha = (NSUInteger)(a * 255) & 0xFF;
} break;
default:
red = green = blue = alpha = 0;
}
NSUInteger intColor = (alpha << 24) | (red << 16) | (green << 8) | blue;
return
@{@"__type__" : @"color", @"__mutable__" : @NO, @"value" : @(intColor)};
}
@end
#endif

View File

@@ -0,0 +1,15 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <FlipperKit/SKMacros.h>
FB_LINK_REQUIRE_CATEGORY(UIView_SKInvalidation)
@interface UIView (SKInvalidation)
+ (void)enableInvalidation;
@end

View File

@@ -0,0 +1,118 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if FB_SONARKIT_ENABLED
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
#import "SKInvalidation.h"
#import "SKSwizzle.h"
#import "UIView+SKInvalidation.h"
FB_LINKABLE(UIView_SKInvalidation)
@implementation UIView (SKInvalidation)
+ (void)enableInvalidation {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
swizzleMethods(
[self class], @selector(setHidden:), @selector(swizzle_setHidden:));
swizzleMethods(
[self class], @selector(addSubview:), @selector(swizzle_addSubview:));
swizzleMethods(
[self class],
@selector(bringSubviewToFront:),
@selector(swizzle_bringSubviewToFront:));
swizzleMethods(
[self class],
@selector(sendSubviewToBack:),
@selector(swizzle_sendSubviewToBack:));
swizzleMethods(
[self class],
@selector(insertSubview:atIndex:),
@selector(swizzle_insertSubview:atIndex:));
swizzleMethods(
[self class],
@selector(insertSubview:aboveSubview:),
@selector(swizzle_insertSubview:aboveSubview:));
swizzleMethods(
[self class],
@selector(insertSubview:belowSubview:),
@selector(swizzle_insertSubview:belowSubview:));
swizzleMethods(
[self class],
@selector(exchangeSubviewAtIndex:withSubviewAtIndex:),
@selector(swizzle_exchangeSubviewAtIndex:withSubviewAtIndex:));
swizzleMethods(
[self class],
@selector(removeFromSuperview),
@selector(swizzle_removeFromSuperview));
});
}
- (void)swizzle_setHidden:(BOOL)hidden {
[self swizzle_setHidden:hidden];
id<SKInvalidationDelegate> delegate =
[SKInvalidation sharedInstance].delegate;
if (delegate != nil) {
[delegate invalidateNode:self.superview];
}
}
- (void)swizzle_addSubview:(UIView*)view {
[self swizzle_addSubview:view];
[[SKInvalidation sharedInstance].delegate invalidateNode:self];
}
- (void)swizzle_bringSubviewToFront:(UIView*)subview {
[self swizzle_bringSubviewToFront:subview];
[[SKInvalidation sharedInstance].delegate invalidateNode:self];
}
- (void)swizzle_sendSubviewToBack:(UIView*)subview {
[self swizzle_sendSubviewToBack:subview];
[[SKInvalidation sharedInstance].delegate invalidateNode:self];
}
- (void)swizzle_insertSubview:(UIView*)subview atIndex:(NSInteger)index {
[self swizzle_insertSubview:subview atIndex:index];
[[SKInvalidation sharedInstance].delegate invalidateNode:self];
}
- (void)swizzle_insertSubview:(UIView*)subview
aboveSubview:(UIView*)siblingSubview {
[self swizzle_insertSubview:subview aboveSubview:siblingSubview];
[[SKInvalidation sharedInstance].delegate invalidateNode:self];
}
- (void)swizzle_insertSubview:(UIView*)subview
belowSubview:(UIView*)siblingSubview {
[self swizzle_insertSubview:subview belowSubview:siblingSubview];
[[SKInvalidation sharedInstance].delegate invalidateNode:self];
}
- (void)swizzle_exchangeSubviewAtIndex:(NSInteger)index1
withSubviewAtIndex:(NSInteger)index2 {
[self swizzle_exchangeSubviewAtIndex:index1 withSubviewAtIndex:index2];
[[SKInvalidation sharedInstance].delegate invalidateNode:self];
}
- (void)swizzle_removeFromSuperview {
UIView* oldSuperview = self.superview;
// Be careful that we always call the swizzled implementation
// before any early returns or mischief below!
[self swizzle_removeFromSuperview];
if (oldSuperview) {
[[SKInvalidation sharedInstance].delegate invalidateNode:oldSuperview];
}
}
@end
#endif

View File

@@ -0,0 +1,11 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
@interface SKHiddenWindow : UIWindow
@end

View File

@@ -0,0 +1,16 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if FB_SONARKIT_ENABLED
#import "SKHiddenWindow.h"
@implementation SKHiddenWindow
@end
#endif

View File

@@ -0,0 +1,19 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
/*
A hash function is needed in order to use NSObject classes
as keys in C++ STL
*/
class SKObjectHash {
public:
size_t operator()(const NSObject* x) const {
return (size_t)[x hash];
}
};

View File

@@ -0,0 +1,10 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
void swizzleMethods(Class cls, SEL original, SEL swissled);

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if FB_SONARKIT_ENABLED
#import "SKSwizzle.h"
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
void swizzleMethods(Class cls, SEL original, SEL swissled) {
Method originalMethod = class_getInstanceMethod(cls, original);
Method swissledMethod = class_getInstanceMethod(cls, swissled);
BOOL didAddMethod = class_addMethod(
cls,
original,
method_getImplementation(swissledMethod),
method_getTypeEncoding(swissledMethod));
if (didAddMethod) {
class_replaceMethod(
cls,
swissled,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swissledMethod);
}
}
#endif

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#define APPLY_ENUM_TO_YOGA_PROPERTY(varName, enumName) \
^(NSString * newValue) { \
NSNumber* varName = \
[[enumName##EnumMap allKeysForObject:newValue] lastObject]; \
if (varName == nil) { \
return; \
} \
node.yoga.varName = (enumName)[varName unsignedIntegerValue]; \
}
#define APPLY_VALUE_TO_YGVALUE(varName) \
^(NSNumber * value) { \
YGValue newValue = node.yoga.varName; \
newValue.value = [value floatValue]; \
node.yoga.varName = newValue; \
}
#define APPLY_UNIT_TO_YGVALUE(varName, enumName) \
^(NSString * value) { \
NSNumber* varName = \
[[enumName##EnumMap allKeysForObject:value] lastObject]; \
if (varName == nil) { \
return; \
} \
YGValue newValue = node.yoga.varName; \
newValue.unit = (enumName)[varName unsignedIntegerValue]; \
node.yoga.varName = newValue; \
}

View File

@@ -0,0 +1,16 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <FlipperKitLayoutHelpers/SKNodeDescriptor.h>
@class SKDescriptorMapper;
@interface SKApplicationDescriptor : SKNodeDescriptor<UIApplication*>
@end

View File

@@ -0,0 +1,71 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if FB_SONARKIT_ENABLED
#import "SKApplicationDescriptor.h"
#import <FlipperKitLayoutHelpers/SKHiddenWindow.h>
#import <objc/runtime.h>
@implementation SKApplicationDescriptor
- (NSString*)identifierForNode:(UIApplication*)node {
return [NSString stringWithFormat:@"%p", node];
}
- (NSUInteger)childCountForNode:(UIApplication*)node {
return [[self visibleChildrenForNode:node] count];
}
- (id)childForNode:(UIApplication*)node atIndex:(NSUInteger)index {
return [self visibleChildrenForNode:node][index];
}
- (void)setHighlighted:(BOOL)highlighted forNode:(UIApplication*)node {
SKNodeDescriptor* windowDescriptor =
[self descriptorForClass:[UIWindow class]];
[windowDescriptor setHighlighted:highlighted forNode:[node keyWindow]];
}
- (void)hitTest:(SKTouch*)touch forNode:(UIApplication*)node {
bool finish = true;
for (NSInteger index = [self childCountForNode:node] - 1; index >= 0;
index--) {
UIWindow* child = [self childForNode:node atIndex:index];
if (child.isHidden || child.alpha <= 0) {
continue;
}
if ([touch containedIn:child.frame]) {
[touch continueWithChildIndex:index withOffset:child.frame.origin];
finish = false;
}
}
if (finish) {
[touch finish];
}
}
- (NSArray<UIWindow*>*)visibleChildrenForNode:(UIApplication*)node {
NSMutableArray<UIWindow*>* children = [NSMutableArray new];
for (UIWindow* window in node.windows) {
if ([window isKindOfClass:[SKHiddenWindow class]] ||
[window
isKindOfClass:objc_lookUpClass("FBAccessibilityOverlayWindow")] ||
[window isKindOfClass:objc_lookUpClass("UITextEffectsWindow")] ||
[window isKindOfClass:objc_lookUpClass("FBStatusBarTrackingWindow")]) {
continue;
}
[children addObject:window];
}
return children;
}
@end
#endif

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <FlipperKitLayoutHelpers/SKNodeDescriptor.h>
@class UIButton;
@class SKDescriptorMapper;
@interface SKButtonDescriptor : SKNodeDescriptor<UIButton*>
@end

View File

@@ -0,0 +1,96 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if FB_SONARKIT_ENABLED
#import "SKButtonDescriptor.h"
#import <FlipperKitLayoutHelpers/SKObject.h>
#import <FlipperKitLayoutHelpers/UIColor+SKSonarValueCoder.h>
@implementation SKButtonDescriptor
- (NSString*)identifierForNode:(UIButton*)node {
return [NSString stringWithFormat:@"%p", node];
}
- (NSUInteger)childCountForNode:(UIButton*)node {
return 0;
}
- (id)childForNode:(UIButton*)node atIndex:(NSUInteger)index {
return nil;
}
- (NSArray<SKNamed<NSDictionary*>*>*)dataForNode:(UIButton*)node {
SKNodeDescriptor* viewDescriptor = [self descriptorForClass:[UIView class]];
auto* viewData = [viewDescriptor dataForNode:node];
NSMutableArray* data = [NSMutableArray new];
[data addObjectsFromArray:viewData];
[data addObject:[SKNamed
newWithName:@"UIButton"
withValue:@{
@"focused" : @(node.focused),
@"enabled" : SKMutableObject(@(node.enabled)),
@"highlighted" : SKMutableObject(@(node.highlighted)),
@"titleEdgeInsets" : SKObject(node.titleEdgeInsets),
@"titleLabel" : SKMutableObject(
node.titleLabel.attributedText.string
.stringByStandardizingPath),
@"currentTitleColor" :
SKMutableObject(node.currentTitleColor),
}]];
return data;
}
- (NSDictionary<NSString*, SKNodeUpdateData>*)dataMutationsForNode:
(UIButton*)node {
NSDictionary* buttonMutations =
@{@"UIButton.titleLabel" : ^(NSString* newValue){
[node setTitle:newValue forState:node.state];
}
,
@"UIButton.currentTitleColor": ^(NSNumber *newValue) {
[node setTitleColor: [UIColor fromSonarValue: newValue] forState: node.state];
},
@"UIButton.highlighted": ^(NSNumber *highlighted) {
[node setHighlighted: [highlighted boolValue]];
},
@"UIButton.enabled": ^(NSNumber *enabled) {
[node setEnabled: [enabled boolValue]];
}
}
;
SKNodeDescriptor* viewDescriptor = [self descriptorForClass:[UIView class]];
NSDictionary* viewMutations = [viewDescriptor dataMutationsForNode:node];
NSMutableDictionary* mutations = [NSMutableDictionary new];
[mutations addEntriesFromDictionary:buttonMutations];
[mutations addEntriesFromDictionary:viewMutations];
return mutations;
}
- (NSArray<SKNamed<NSString*>*>*)attributesForNode:(UIScrollView*)node {
SKNodeDescriptor* descriptor = [self descriptorForClass:[UIView class]];
return [descriptor attributesForNode:node];
}
- (void)setHighlighted:(BOOL)highlighted forNode:(UIButton*)node {
SKNodeDescriptor* viewDescriptor = [self descriptorForClass:[UIView class]];
[viewDescriptor setHighlighted:highlighted forNode:node];
}
- (void)hitTest:(SKTouch*)touch forNode:(UIButton*)node {
[touch finish];
}
@end
#endif

View File

@@ -0,0 +1,16 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <UIKit/UIKit.h>
#import <FlipperKitLayoutHelpers/SKNodeDescriptor.h>
@class SKDescriptorMapper;
@interface SKScrollViewDescriptor : SKNodeDescriptor<UIScrollView*>
@end

View File

@@ -0,0 +1,88 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if FB_SONARKIT_ENABLED
#import "SKScrollViewDescriptor.h"
@implementation SKScrollViewDescriptor
- (NSString*)identifierForNode:(UIScrollView*)node {
SKNodeDescriptor* descriptor = [self descriptorForClass:[UIView class]];
return [descriptor identifierForNode:node];
}
- (NSUInteger)childCountForNode:(UIScrollView*)node {
SKNodeDescriptor* descriptor = [self descriptorForClass:[UIView class]];
return [descriptor childCountForNode:node];
}
- (id)childForNode:(UIScrollView*)node atIndex:(NSUInteger)index {
SKNodeDescriptor* descriptor = [self descriptorForClass:[UIView class]];
return [descriptor childForNode:node atIndex:index];
}
- (id)dataForNode:(UIScrollView*)node {
SKNodeDescriptor* descriptor = [self descriptorForClass:[UIView class]];
return [descriptor dataForNode:node];
}
- (id)dataMutationsForNode:(UIScrollView*)node {
SKNodeDescriptor* descriptor = [self descriptorForClass:[UIView class]];
return [descriptor dataMutationsForNode:node];
}
- (NSArray<SKNamed<NSString*>*>*)attributesForNode:(UIScrollView*)node {
SKNodeDescriptor* descriptor = [self descriptorForClass:[UIView class]];
return [descriptor attributesForNode:node];
}
- (void)setHighlighted:(BOOL)highlighted forNode:(UIScrollView*)node {
SKNodeDescriptor* descriptor = [self descriptorForClass:[UIView class]];
[descriptor setHighlighted:highlighted forNode:node];
}
- (void)hitTest:(SKTouch*)touch forNode:(UIScrollView*)node {
bool finish = true;
for (NSInteger index = [self childCountForNode:node] - 1; index >= 0;
index--) {
id<NSObject> childNode = [self childForNode:node atIndex:index];
CGRect frame;
if ([childNode isKindOfClass:[UIViewController class]]) {
UIViewController* child = (UIViewController*)childNode;
if (child.view.isHidden) {
continue;
}
frame = child.view.frame;
} else {
UIView* child = (UIView*)childNode;
if (child.isHidden) {
continue;
}
frame = child.frame;
}
frame.origin.x -= node.contentOffset.x;
frame.origin.y -= node.contentOffset.y;
if ([touch containedIn:frame]) {
[touch continueWithChildIndex:index withOffset:frame.origin];
finish = false;
}
}
if (finish) {
[touch finish];
}
}
@end
#endif

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if FB_SONARKIT_ENABLED
#import <UIKit/UIKit.h>
#import <FlipperKitLayoutHelpers/SKNodeDescriptor.h>
@class SKDescriptorMapper;
@interface SKViewControllerDescriptor : SKNodeDescriptor<UIViewController*>
@end
#endif

View File

@@ -0,0 +1,45 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if FB_SONARKIT_ENABLED
#import "SKViewControllerDescriptor.h"
@implementation SKViewControllerDescriptor
- (NSString*)identifierForNode:(UIViewController*)node {
return [NSString stringWithFormat:@"%p", node];
}
- (NSUInteger)childCountForNode:(UIViewController*)node {
return 1;
}
- (id)childForNode:(UIViewController*)node atIndex:(NSUInteger)index {
return node.view;
}
- (void)setHighlightedForNode:(UIViewController*)node {
}
- (NSArray<SKNamed<NSString*>*>*)attributesForNode:(UIViewController*)node {
return @[ [SKNamed newWithName:@"addr"
withValue:[NSString stringWithFormat:@"%p", node]] ];
}
- (void)setHighlighted:(BOOL)highlighted forNode:(UIViewController*)node {
SKNodeDescriptor* descriptor = [self descriptorForClass:[UIView class]];
[descriptor setHighlighted:highlighted forNode:node.view];
}
- (void)hitTest:(SKTouch*)touch forNode:(UIViewController*)node {
[touch continueWithChildIndex:0 withOffset:(CGPoint){0, 0}];
}
@end
#endif

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if FB_SONARKIT_ENABLED
#import <UIKit/UIKit.h>
#import <FlipperKitLayoutHelpers/SKNodeDescriptor.h>
@class SKDescriptorMapper;
@interface SKViewDescriptor : SKNodeDescriptor<UIView*>
@end
#endif

View File

@@ -0,0 +1,670 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if FB_SONARKIT_ENABLED
#import "SKViewDescriptor.h"
#import <FlipperKitHighlightOverlay/SKHighlightOverlay.h>
#import <FlipperKitLayoutHelpers/FlipperKitLayoutDescriptorMapperProtocol.h>
#import <FlipperKitLayoutHelpers/SKHiddenWindow.h>
#import <FlipperKitLayoutHelpers/SKNamed.h>
#import <FlipperKitLayoutHelpers/SKObject.h>
#import <FlipperKitLayoutHelpers/SKYogaKitHelper.h>
#import <FlipperKitLayoutHelpers/UIColor+SKSonarValueCoder.h>
#import <YogaKit/UIView+Yoga.h>
@implementation SKViewDescriptor
static NSDictionary* YGDirectionEnumMap = nil;
static NSDictionary* YGFlexDirectionEnumMap = nil;
static NSDictionary* YGJustifyEnumMap = nil;
static NSDictionary* YGAlignEnumMap = nil;
static NSDictionary* YGPositionTypeEnumMap = nil;
static NSDictionary* YGWrapEnumMap = nil;
static NSDictionary* YGOverflowEnumMap = nil;
static NSDictionary* YGDisplayEnumMap = nil;
static NSDictionary* YGUnitEnumMap = nil;
- (instancetype)initWithDescriptorMapper:
(id<SKDescriptorMapperProtocol>)mapper {
if (self = [super initWithDescriptorMapper:mapper]) {
initEnumDictionaries();
}
return self;
}
- (NSString*)identifierForNode:(UIView*)node {
return [NSString stringWithFormat:@"%p", node];
}
- (NSUInteger)childCountForNode:(UIView*)node {
return [[self validChildrenForNode:node] count];
}
- (id)childForNode:(UIView*)node atIndex:(NSUInteger)index {
return [[self validChildrenForNode:node] objectAtIndex:index];
}
- (NSArray*)validChildrenForNode:(UIView*)node {
NSMutableArray* validChildren = [NSMutableArray new];
// Use UIViewControllers for children which responds to a different
// viewController than their parent
for (UIView* child in node.subviews) {
BOOL responderIsUIViewController =
[child.nextResponder isKindOfClass:[UIViewController class]];
if (!child.isHidden) {
if (responderIsUIViewController &&
child.nextResponder != node.nextResponder) {
[validChildren addObject:child.nextResponder];
} else {
[validChildren addObject:child];
}
}
}
return validChildren;
}
- (NSArray<SKNamed<NSDictionary*>*>*)dataForNode:(UIView*)node {
return [NSArray
arrayWithObjects:
[SKNamed
newWithName:@"UIView"
withValue:@{
@"frame" : SKMutableObject(node.frame),
@"bounds" : SKObject(node.bounds),
@"center" : SKObject(node.center),
@"layoutMargins" : SKObject(node.layoutMargins),
@"clipsToBounds" : @(node.clipsToBounds),
@"alpha" : SKMutableObject(@(node.alpha)),
@"tag" : @(node.tag),
@"backgroundColor" : SKMutableObject(node.backgroundColor)
}],
[SKNamed
newWithName:@"CALayer"
withValue:@{
@"shadowColor" : SKMutableObject(
[UIColor colorWithCGColor:node.layer.shadowColor]),
@"shadowOpacity" :
SKMutableObject(@(node.layer.shadowOpacity)),
@"shadowRadius" : SKMutableObject(@(node.layer.shadowRadius)),
@"shadowOffset" : SKMutableObject(node.layer.shadowOffset),
@"backgroundColor" : SKMutableObject(
[UIColor colorWithCGColor:node.layer.backgroundColor]),
@"borderColor" : SKMutableObject(
[UIColor colorWithCGColor:node.layer.borderColor]),
@"borderWidth" : SKMutableObject(@(node.layer.borderWidth)),
@"cornerRadius" : SKMutableObject(@(node.layer.cornerRadius)),
@"masksToBounds" :
SKMutableObject(@(node.layer.masksToBounds)),
}],
[SKNamed newWithName:@"Accessibility"
withValue:@{
@"isAccessibilityElement" :
SKMutableObject(@(node.isAccessibilityElement)),
@"accessibilityLabel" :
SKMutableObject(node.accessibilityLabel ?: @""),
@"accessibilityIdentifier" :
SKMutableObject(node.accessibilityIdentifier ?: @""),
@"accessibilityValue" :
SKMutableObject(node.accessibilityValue ?: @""),
@"accessibilityHint" :
SKMutableObject(node.accessibilityHint ?: @""),
@"accessibilityTraits" :
AccessibilityTraitsDict(node.accessibilityTraits),
@"accessibilityViewIsModal" :
SKMutableObject(@(node.accessibilityViewIsModal)),
@"shouldGroupAccessibilityChildren" : SKMutableObject(
@(node.shouldGroupAccessibilityChildren)),
}],
!node.isYogaEnabled
? nil
: [SKNamed
newWithName:@"YGLayout"
withValue:@{
@"direction" : SKMutableObject(
YGDirectionEnumMap[@(node.yoga.direction)]),
@"justifyContent" : SKMutableObject(
YGJustifyEnumMap[@(node.yoga.justifyContent)]),
@"aligns" : @{
@"alignContent" : SKMutableObject(
YGAlignEnumMap[@(node.yoga.alignContent)]),
@"alignItems" : SKMutableObject(
YGAlignEnumMap[@(node.yoga.alignItems)]),
@"alignSelf" : SKMutableObject(
YGAlignEnumMap[@(node.yoga.alignSelf)]),
},
@"position" : @{
@"type" : SKMutableObject(
YGPositionTypeEnumMap[@(node.yoga.position)]),
@"left" : SKYGValueObject(node.yoga.left),
@"top" : SKYGValueObject(node.yoga.top),
@"right" : SKYGValueObject(node.yoga.right),
@"bottom" : SKYGValueObject(node.yoga.bottom),
@"start" : SKYGValueObject(node.yoga.start),
@"end" : SKYGValueObject(node.yoga.end),
},
@"overflow" : SKMutableObject(
YGOverflowEnumMap[@(node.yoga.overflow)]),
@"display" : SKMutableObject(
YGDisplayEnumMap[@(node.yoga.display)]),
@"flex" : @{
@"flexDirection" :
SKMutableObject(YGFlexDirectionEnumMap[
@(node.yoga.flexDirection)]),
@"flexWrap" : SKMutableObject(
YGWrapEnumMap[@(node.yoga.flexWrap)]),
@"flexGrow" : SKMutableObject(@(node.yoga.flexGrow)),
@"flexShrink" :
SKMutableObject(@(node.yoga.flexShrink)),
@"flexBasis" : SKYGValueObject(node.yoga.flexBasis),
},
@"margin" : @{
@"left" : SKYGValueObject(node.yoga.marginLeft),
@"top" : SKYGValueObject(node.yoga.marginTop),
@"right" : SKYGValueObject(node.yoga.marginRight),
@"bottom" : SKYGValueObject(node.yoga.marginBottom),
@"start" : SKYGValueObject(node.yoga.marginStart),
@"end" : SKYGValueObject(node.yoga.marginEnd),
@"horizontal" :
SKYGValueObject(node.yoga.marginHorizontal),
@"vertical" :
SKYGValueObject(node.yoga.marginVertical),
@"all" : SKYGValueObject(node.yoga.margin),
},
@"padding" : @{
@"left" : SKYGValueObject(node.yoga.paddingLeft),
@"top" : SKYGValueObject(node.yoga.paddingTop),
@"right" : SKYGValueObject(node.yoga.paddingRight),
@"bottom" : SKYGValueObject(node.yoga.paddingBottom),
@"start" : SKYGValueObject(node.yoga.paddingStart),
@"end" : SKYGValueObject(node.yoga.paddingEnd),
@"horizontal" :
SKYGValueObject(node.yoga.paddingHorizontal),
@"vertical" :
SKYGValueObject(node.yoga.paddingVertical),
@"all" : SKYGValueObject(node.yoga.padding),
},
@"border" : @{
@"leftWidth" :
SKMutableObject(@(node.yoga.borderLeftWidth)),
@"topWidth" :
SKMutableObject(@(node.yoga.borderTopWidth)),
@"rightWidth" :
SKMutableObject(@(node.yoga.borderRightWidth)),
@"bottomWidth" :
SKMutableObject(@(node.yoga.borderBottomWidth)),
@"startWidth" :
SKMutableObject(@(node.yoga.borderStartWidth)),
@"endWidth" :
SKMutableObject(@(node.yoga.borderEndWidth)),
@"all" : SKMutableObject(@(node.yoga.borderWidth)),
},
@"dimensions" : @{
@"width" : SKYGValueObject(node.yoga.width),
@"height" : SKYGValueObject(node.yoga.height),
@"minWidth" : SKYGValueObject(node.yoga.minWidth),
@"minHeight" : SKYGValueObject(node.yoga.minHeight),
@"maxWidth" : SKYGValueObject(node.yoga.maxWidth),
@"maxHeight" : SKYGValueObject(node.yoga.maxHeight),
},
@"aspectRatio" :
SKMutableObject(@(node.yoga.aspectRatio)),
@"resolvedDirection" : SKObject(
YGDirectionEnumMap[@(node.yoga.resolvedDirection)]),
}],
nil];
}
- (NSDictionary<NSString*, SKNodeUpdateData>*)dataMutationsForNode:
(UIView*)node {
NSDictionary<NSString*, SKNodeUpdateData>* dataMutations = @{
// UIView
@"UIView.alpha" : ^(NSNumber* value){
node.alpha = [value floatValue];
}
,
@"UIView.backgroundColor": ^(NSNumber *value) {
node.backgroundColor = [UIColor fromSonarValue: value];
},
@"UIView.frame.origin.y": ^(NSNumber *value) {
CGRect frame = node.frame;
frame.origin.y = [value floatValue];
node.frame = frame;
},
@"UIView.frame.origin.x": ^(NSNumber *value) {
CGRect frame = node.frame;
frame.origin.x = [value floatValue];
node.frame = frame;
},
@"UIView.frame.size.width": ^(NSNumber *value) {
CGRect frame = node.frame;
frame.size.width = [value floatValue];
node.frame = frame;
},
@"UIView.frame.size.height": ^(NSNumber *value) {
CGRect frame = node.frame;
frame.size.width = [value floatValue];
node.frame = frame;
},
// CALayer
@"CALayer.shadowColor": ^(NSNumber *value) {
node.layer.shadowColor = [UIColor fromSonarValue:value].CGColor;
},
@"CALayer.shadowOpacity": ^(NSNumber *value) {
node.layer.shadowOpacity = [value floatValue];
},
@"CALayer.shadowRadius": ^(NSNumber *value) {
node.layer.shadowRadius = [value floatValue];
},
@"CALayer.shadowOffset.width": ^(NSNumber *value) {
CGSize offset = node.layer.shadowOffset;
offset.width = [value floatValue];
node.layer.shadowOffset = offset;
},
@"CALayer.shadowOffset.height": ^(NSNumber *value) {
CGSize offset = node.layer.shadowOffset;
offset.height = [value floatValue];
node.layer.shadowOffset = offset;
},
@"CALayer.backgroundColor": ^(NSNumber *value) {
node.layer.backgroundColor = [UIColor fromSonarValue:value].CGColor;
},
@"CALayer.borderColor": ^(NSNumber *value) {
node.layer.borderColor = [UIColor fromSonarValue:value].CGColor;
},
@"CALayer.borderWidth": ^(NSNumber *value) {
node.layer.borderWidth = [value floatValue];
},
@"CALayer.cornerRadius": ^(NSNumber *value) {
node.layer.cornerRadius = [value floatValue];
},
@"CALayer.masksToBounds": ^(NSNumber *value) {
node.layer.masksToBounds = [value boolValue];
},
// YGLayout
@"YGLayout.direction": APPLY_ENUM_TO_YOGA_PROPERTY(direction, YGDirection),
@"YGLayout.justifyContent": APPLY_ENUM_TO_YOGA_PROPERTY(justifyContent, YGJustify),
@"YGLayout.aligns.alignContent": APPLY_ENUM_TO_YOGA_PROPERTY(alignContent, YGAlign),
@"YGLayout.aligns.alignItems": APPLY_ENUM_TO_YOGA_PROPERTY(alignItems, YGAlign),
@"YGLayout.aligns.alignSelf": APPLY_ENUM_TO_YOGA_PROPERTY(alignSelf, YGAlign),
@"YGLayout.position.type": APPLY_ENUM_TO_YOGA_PROPERTY(position, YGPositionType),
@"YGLayout.position.left.value": APPLY_VALUE_TO_YGVALUE(left),
@"YGLayout.position.left.unit": APPLY_UNIT_TO_YGVALUE(left, YGUnit),
@"YGLayout.position.top.value": APPLY_VALUE_TO_YGVALUE(top),
@"YGLayout.position.top.unit": APPLY_UNIT_TO_YGVALUE(top, YGUnit),
@"YGLayout.position.right.value": APPLY_VALUE_TO_YGVALUE(right),
@"YGLayout.position.right.unit": APPLY_UNIT_TO_YGVALUE(right, YGUnit),
@"YGLayout.position.bottom.value": APPLY_VALUE_TO_YGVALUE(bottom),
@"YGLayout.position.bottom.unit": APPLY_UNIT_TO_YGVALUE(bottom, YGUnit),
@"YGLayout.position.start.value": APPLY_VALUE_TO_YGVALUE(start),
@"YGLayout.position.start.unit": APPLY_UNIT_TO_YGVALUE(start, YGUnit),
@"YGLayout.position.end.value": APPLY_VALUE_TO_YGVALUE(end),
@"YGLayout.position.end.unit": APPLY_UNIT_TO_YGVALUE(end, YGUnit),
@"YGLayout.overflow": APPLY_ENUM_TO_YOGA_PROPERTY(overflow, YGOverflow),
@"YGLayout.display": APPLY_ENUM_TO_YOGA_PROPERTY(display, YGDisplay),
@"YGLayout.flex.flexDirection": APPLY_ENUM_TO_YOGA_PROPERTY(flexDirection, YGFlexDirection),
@"YGLayout.flex.flexWrap": APPLY_ENUM_TO_YOGA_PROPERTY(flexWrap, YGWrap),
@"YGLayout.flex.flexGrow": ^(NSNumber *value) {
node.yoga.flexGrow = [value floatValue];
},
@"YGLayout.flex.flexShrink": ^(NSNumber *value) {
node.yoga.flexShrink = [value floatValue];
},
@"YGLayout.flex.flexBasis.value": APPLY_VALUE_TO_YGVALUE(flexBasis),
@"YGLayout.flex.flexBasis.unit": APPLY_UNIT_TO_YGVALUE(flexBasis, YGUnit),
@"YGLayout.margin.left.value": APPLY_VALUE_TO_YGVALUE(marginLeft),
@"YGLayout.margin.left.unit": APPLY_UNIT_TO_YGVALUE(marginLeft, YGUnit),
@"YGLayout.margin.top.value": APPLY_VALUE_TO_YGVALUE(marginTop),
@"YGLayout.margin.top.unit": APPLY_UNIT_TO_YGVALUE(marginTop, YGUnit),
@"YGLayout.margin.right.value": APPLY_VALUE_TO_YGVALUE(marginRight),
@"YGLayout.margin.right.unit": APPLY_UNIT_TO_YGVALUE(marginRight, YGUnit),
@"YGLayout.margin.bottom.value": APPLY_VALUE_TO_YGVALUE(marginBottom),
@"YGLayout.margin.bottom.unit": APPLY_UNIT_TO_YGVALUE(marginBottom, YGUnit),
@"YGLayout.margin.start.value": APPLY_VALUE_TO_YGVALUE(marginStart),
@"YGLayout.margin.start.unit": APPLY_UNIT_TO_YGVALUE(marginStart, YGUnit),
@"YGLayout.margin.end.value": APPLY_VALUE_TO_YGVALUE(marginEnd),
@"YGLayout.margin.end.unit": APPLY_UNIT_TO_YGVALUE(marginEnd, YGUnit),
@"YGLayout.margin.horizontal.value": APPLY_VALUE_TO_YGVALUE(marginHorizontal),
@"YGLayout.margin.horizontal.unit": APPLY_UNIT_TO_YGVALUE(marginHorizontal, YGUnit),
@"YGLayout.margin.vertical.value": APPLY_VALUE_TO_YGVALUE(marginVertical),
@"YGLayout.margin.vertical.unit": APPLY_UNIT_TO_YGVALUE(marginVertical, YGUnit),
@"YGLayout.margin.all.value": APPLY_VALUE_TO_YGVALUE(margin),
@"YGLayout.margin.all.unit": APPLY_UNIT_TO_YGVALUE(margin, YGUnit),
@"YGLayout.padding.left.value": APPLY_VALUE_TO_YGVALUE(paddingLeft),
@"YGLayout.padding.left.unit": APPLY_UNIT_TO_YGVALUE(paddingLeft, YGUnit),
@"YGLayout.padding.top.value": APPLY_VALUE_TO_YGVALUE(paddingTop),
@"YGLayout.padding.top.unit": APPLY_UNIT_TO_YGVALUE(paddingTop, YGUnit),
@"YGLayout.padding.right.value": APPLY_VALUE_TO_YGVALUE(paddingRight),
@"YGLayout.padding.right.unit": APPLY_UNIT_TO_YGVALUE(paddingRight, YGUnit),
@"YGLayout.padding.bottom.value": APPLY_VALUE_TO_YGVALUE(paddingBottom),
@"YGLayout.padding.bottom.unit": APPLY_UNIT_TO_YGVALUE(paddingBottom, YGUnit),
@"YGLayout.padding.start.value": APPLY_VALUE_TO_YGVALUE(paddingStart),
@"YGLayout.padding.start.unit": APPLY_UNIT_TO_YGVALUE(paddingStart, YGUnit),
@"YGLayout.padding.end.value": APPLY_VALUE_TO_YGVALUE(paddingEnd),
@"YGLayout.padding.end.unit": APPLY_UNIT_TO_YGVALUE(paddingEnd, YGUnit),
@"YGLayout.padding.horizontal.value": APPLY_VALUE_TO_YGVALUE(paddingHorizontal),
@"YGLayout.padding.horizontal.unit": APPLY_UNIT_TO_YGVALUE(paddingHorizontal, YGUnit),
@"YGLayout.padding.vertical.value": APPLY_VALUE_TO_YGVALUE(paddingVertical),
@"YGLayout.padding.vertical.unit": APPLY_UNIT_TO_YGVALUE(paddingVertical, YGUnit),
@"YGLayout.padding.all.value": APPLY_VALUE_TO_YGVALUE(padding),
@"YGLayout.padding.all.unit": APPLY_UNIT_TO_YGVALUE(padding, YGUnit),
@"YGLayout.border.leftWidth": ^(NSNumber *value) {
node.yoga.borderLeftWidth = [value floatValue];
},
@"YGLayout.border.topWidth": ^(NSNumber *value) {
node.yoga.borderTopWidth = [value floatValue];
},
@"YGLayout.border.rightWidth": ^(NSNumber *value) {
node.yoga.borderRightWidth = [value floatValue];
},
@"YGLayout.border.bottomWidth": ^(NSNumber *value) {
node.yoga.borderBottomWidth = [value floatValue];
},
@"YGLayout.border.startWidth": ^(NSNumber *value) {
node.yoga.borderStartWidth = [value floatValue];
},
@"YGLayout.border.endWidth": ^(NSNumber *value) {
node.yoga.borderEndWidth = [value floatValue];
},
@"YGLayout.border.all": ^(NSNumber *value) {
node.yoga.borderWidth = [value floatValue];
},
@"YGLayout.dimensions.width.value": APPLY_VALUE_TO_YGVALUE(width),
@"YGLayout.dimensions.width.unit": APPLY_UNIT_TO_YGVALUE(width, YGUnit),
@"YGLayout.dimensions.height.value": APPLY_VALUE_TO_YGVALUE(height),
@"YGLayout.dimensions.height.unit": APPLY_UNIT_TO_YGVALUE(height, YGUnit),
@"YGLayout.dimensions.minWidth.value": APPLY_VALUE_TO_YGVALUE(minWidth),
@"YGLayout.dimensions.minWidth.unit": APPLY_UNIT_TO_YGVALUE(minWidth, YGUnit),
@"YGLayout.dimensions.minHeight.value": APPLY_VALUE_TO_YGVALUE(minHeight),
@"YGLayout.dimensions.minHeight.unit": APPLY_UNIT_TO_YGVALUE(minHeight, YGUnit),
@"YGLayout.dimensions.maxWidth.value": APPLY_VALUE_TO_YGVALUE(maxWidth),
@"YGLayout.dimensions.maxWidth.unit": APPLY_UNIT_TO_YGVALUE(maxWidth, YGUnit),
@"YGLayout.dimensions.maxHeight.value": APPLY_VALUE_TO_YGVALUE(maxHeight),
@"YGLayout.dimensions.maxHeight.unit": APPLY_UNIT_TO_YGVALUE(maxHeight, YGUnit),
@"YGLayout.aspectRatio": ^(NSNumber *value) {
node.yoga.aspectRatio = [value floatValue];
},
// Accessibility
@"Accessibility.isAccessibilityElement": ^(NSNumber *value) {
node.isAccessibilityElement = [value boolValue];
},
@"Accessibility.accessibilityLabel": ^(NSString *value) {
node.accessibilityLabel = value;
},
@"Accessibility.accessibilityValue": ^(NSString *value) {
node.accessibilityValue = value;
},
@"Accessibility.accessibilityHint": ^(NSString *value) {
node.accessibilityHint = value;
},
@"Accessibility.accessibilityTraits.UIAccessibilityTraitButton": ^(NSNumber *value) {
node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitButton, [value boolValue]);
},
@"Accessibility.accessibilityTraits.UIAccessibilityTraitLink": ^(NSNumber *value) {
node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitLink, [value boolValue]);
},
@"Accessibility.accessibilityTraits.UIAccessibilityTraitHeader": ^(NSNumber *value) {
node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitHeader, [value boolValue]);
},
@"Accessibility.accessibilityTraits.UIAccessibilityTraitSearchField": ^(NSNumber *value) {
node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitSearchField, [value boolValue]);
},
@"Accessibility.accessibilityTraits.UIAccessibilityTraitImage": ^(NSNumber *value) {
node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitImage, [value boolValue]);
},
@"Accessibility.accessibilityTraits.UIAccessibilityTraitSelected": ^(NSNumber *value) {
node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitSelected, [value boolValue]);
},
@"Accessibility.accessibilityTraits.UIAccessibilityTraitPlaysSound": ^(NSNumber *value) {
node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitPlaysSound, [value boolValue]);
},
@"Accessibility.accessibilityTraits.UIAccessibilityTraitKeyboardKey": ^(NSNumber *value) {
node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitKeyboardKey, [value boolValue]);
},
@"Accessibility.accessibilityTraits.UIAccessibilityTraitStaticText": ^(NSNumber *value) {
node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitStaticText, [value boolValue]);
},
@"Accessibility.accessibilityTraits.UIAccessibilityTraitSummaryElement": ^(NSNumber *value) {
node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitSummaryElement, [value boolValue]);
},
@"Accessibility.accessibilityTraits.UIAccessibilityTraitNotEnabled": ^(NSNumber *value) {
node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitNotEnabled, [value boolValue]);
},
@"Accessibility.accessibilityTraits.UIAccessibilityTraitUpdatesFrequently": ^(NSNumber *value) {
node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitUpdatesFrequently, [value boolValue]);
},
@"Accessibility.accessibilityTraits.UIAccessibilityTraitStartsMediaSession": ^(NSNumber *value) {
node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitStartsMediaSession, [value boolValue]);
},
@"Accessibility.accessibilityTraits.UIAccessibilityTraitAdjustable": ^(NSNumber *value) {
node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitAdjustable, [value boolValue]);
},
@"Accessibility.accessibilityTraits.UIAccessibilityTraitAllowsDirectInteraction": ^(NSNumber *value) {
node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitAllowsDirectInteraction, [value boolValue]);
},
@"Accessibility.accessibilityTraits.UIAccessibilityTraitCausesPageTurn": ^(NSNumber *value) {
node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitCausesPageTurn, [value boolValue]);
},
@"Accessibility.accessibilityTraits.UIAccessibilityTraitTabBar": ^(NSNumber *value) {
if (@available(iOS 10.0, *)) {
node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitTabBar, [value boolValue]);
}
},
@"Accessibility.accessibilityViewIsModal": ^(NSNumber *value) {
node.accessibilityViewIsModal = [value boolValue];
},
@"Accessibility.shouldGroupAccessibilityChildren": ^(NSNumber *value) {
node.shouldGroupAccessibilityChildren = [value boolValue];
},
}
;
if (@available(iOS 10.0, *)) {
NSMutableDictionary<NSString*, SKNodeUpdateData>* latestDataMutations =
[dataMutations mutableCopy];
latestDataMutations
[@"Accessibility.accessibilityTraits.UIAccessibilityTraitTabBar"] =
^(NSNumber* value) {
node.accessibilityTraits = AccessibilityTraitsToggle(
node.accessibilityTraits,
UIAccessibilityTraitTabBar,
[value boolValue]);
};
dataMutations = latestDataMutations;
}
return dataMutations;
}
- (NSArray<SKNamed<NSString*>*>*)attributesForNode:(UIView*)node {
return @[ [SKNamed newWithName:@"addr"
withValue:[NSString stringWithFormat:@"%p", node]] ];
}
- (void)setHighlighted:(BOOL)highlighted forNode:(UIView*)node {
SKHighlightOverlay* overlay = [SKHighlightOverlay sharedInstance];
if (highlighted == YES) {
[overlay mountInView:node withFrame:node.bounds];
} else {
[overlay unmount];
}
}
- (void)hitTest:(SKTouch*)touch forNode:(UIView*)node {
bool finish = true;
for (NSInteger index = [self childCountForNode:node] - 1; index >= 0;
index--) {
id<NSObject> childNode = [self childForNode:node atIndex:index];
UIView* viewForNode = nil;
if ([childNode isKindOfClass:[UIViewController class]]) {
UIViewController* child = (UIViewController*)childNode;
viewForNode = child.view;
} else {
viewForNode = (UIView*)childNode;
}
if (viewForNode.isHidden || viewForNode.alpha <= 0 ||
[[viewForNode class] isEqual:[SKHiddenWindow class]]) {
/*SKHiddenWindow is the pink overlay which is added in window to capture
the gestures.*/
continue;
}
if ([touch containedIn:viewForNode.frame]) {
[touch continueWithChildIndex:index withOffset:viewForNode.frame.origin];
finish = false;
}
}
if (finish) {
[touch finish];
}
}
static void initEnumDictionaries() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
YGDirectionEnumMap = @{
@(YGDirectionInherit) : @"inherit",
@(YGDirectionLTR) : @"LTR",
@(YGDirectionRTL) : @"RTL",
};
YGFlexDirectionEnumMap = @{
@(YGFlexDirectionColumn) : @"column",
@(YGFlexDirectionColumnReverse) : @"column-reverse",
@(YGFlexDirectionRow) : @"row",
@(YGFlexDirectionRowReverse) : @"row-reverse",
};
YGJustifyEnumMap = @{
@(YGJustifyFlexStart) : @"flex-start",
@(YGJustifyCenter) : @"center",
@(YGJustifyFlexEnd) : @"flex-end",
@(YGJustifySpaceBetween) : @"space-between",
@(YGJustifySpaceAround) : @"space-around",
};
YGAlignEnumMap = @{
@(YGAlignAuto) : @"auto",
@(YGAlignFlexStart) : @"flex-start",
@(YGAlignCenter) : @"end",
@(YGAlignFlexEnd) : @"flex-end",
@(YGAlignStretch) : @"stretch",
@(YGAlignBaseline) : @"baseline",
@(YGAlignSpaceBetween) : @"space-between",
@(YGAlignSpaceAround) : @"space-around",
};
YGPositionTypeEnumMap = @{
@(YGPositionTypeRelative) : @"relative",
@(YGPositionTypeAbsolute) : @"absolute",
};
YGWrapEnumMap = @{
@(YGWrapNoWrap) : @"no-wrap",
@(YGWrapWrap) : @"wrap",
@(YGWrapWrapReverse) : @"wrap-reverse",
};
YGOverflowEnumMap = @{
@(YGOverflowVisible) : @"visible",
@(YGOverflowHidden) : @"hidden",
@(YGOverflowScroll) : @"scroll",
};
YGDisplayEnumMap = @{
@(YGDisplayFlex) : @"flex",
@(YGDisplayNone) : @"none",
};
YGUnitEnumMap = @{
@(YGUnitUndefined) : @"undefined",
@(YGUnitPoint) : @"point",
@(YGUnitPercent) : @"percent",
@(YGUnitAuto) : @"auto",
};
});
}
static NSDictionary* SKYGValueObject(YGValue value) {
return @{
@"value" : SKMutableObject(@(value.value)),
@"unit" : SKMutableObject(YGUnitEnumMap[@(value.unit)]),
};
}
/*
Takes the originalTraits, and set all bits from toggleTraits to the toggleValue
e.g. originalTraits = UIAccessibilityTraitButton | UIAccessibilityTraitSelected
toggleTraits = UIAccessibilityTraitImage
toggleValue = YES
return value = UIAccessibilityTraitButton | UIAccessibilityTraitSelected |
UIAccessibilityTraitImage
*/
static UIAccessibilityTraits AccessibilityTraitsToggle(
UIAccessibilityTraits originalTraits,
UIAccessibilityTraits toggleTraits,
BOOL toggleValue) {
// NEGATE all bits of toggleTraits from originalTraits and OR it against
// either toggleTraits or 0 (UIAccessibilityTraitNone) based on toggleValue
UIAccessibilityTraits bitsValue =
toggleValue ? toggleTraits : UIAccessibilityTraitNone;
return (originalTraits & ~(toggleTraits)) | bitsValue;
}
static NSDictionary* AccessibilityTraitsDict(
UIAccessibilityTraits accessibilityTraits) {
NSMutableDictionary* traitsDict = [NSMutableDictionary new];
[traitsDict addEntriesFromDictionary:@{
@"UIAccessibilityTraitButton" : SKMutableObject(
@(!!(accessibilityTraits & UIAccessibilityTraitButton))),
@"UIAccessibilityTraitLink" :
SKMutableObject(@(!!(accessibilityTraits & UIAccessibilityTraitLink))),
@"UIAccessibilityTraitHeader" : SKMutableObject(
@(!!(accessibilityTraits & UIAccessibilityTraitHeader))),
@"UIAccessibilityTraitSearchField" : SKMutableObject(
@(!!(accessibilityTraits & UIAccessibilityTraitSearchField))),
@"UIAccessibilityTraitImage" :
SKMutableObject(@(!!(accessibilityTraits & UIAccessibilityTraitImage))),
@"UIAccessibilityTraitSelected" : SKMutableObject(
@(!!(accessibilityTraits & UIAccessibilityTraitSelected))),
@"UIAccessibilityTraitPlaysSound" : SKMutableObject(
@(!!(accessibilityTraits & UIAccessibilityTraitPlaysSound))),
@"UIAccessibilityTraitKeyboardKey" : SKMutableObject(
@(!!(accessibilityTraits & UIAccessibilityTraitKeyboardKey))),
@"UIAccessibilityTraitStaticText" : SKMutableObject(
@(!!(accessibilityTraits & UIAccessibilityTraitStaticText))),
@"UIAccessibilityTraitSummaryElement" : SKMutableObject(
@(!!(accessibilityTraits & UIAccessibilityTraitSummaryElement))),
@"UIAccessibilityTraitNotEnabled" : SKMutableObject(
@(!!(accessibilityTraits & UIAccessibilityTraitNotEnabled))),
@"UIAccessibilityTraitUpdatesFrequently" : SKMutableObject(
@(!!(accessibilityTraits & UIAccessibilityTraitUpdatesFrequently))),
@"UIAccessibilityTraitStartsMediaSession" : SKMutableObject(
@(!!(accessibilityTraits & UIAccessibilityTraitStartsMediaSession))),
@"UIAccessibilityTraitAdjustable" : SKMutableObject(
@(!!(accessibilityTraits & UIAccessibilityTraitAdjustable))),
@"UIAccessibilityTraitAllowsDirectInteraction" : SKMutableObject(@(
!!(accessibilityTraits & UIAccessibilityTraitAllowsDirectInteraction))),
@"UIAccessibilityTraitCausesPageTurn" : SKMutableObject(
@(!!(accessibilityTraits & UIAccessibilityTraitCausesPageTurn))),
}];
if (@available(iOS 10.0, *)) {
traitsDict[@"UIAccessibilityTraitTabBar"] = SKMutableObject(
@(!!(accessibilityTraits & UIAccessibilityTraitTabBar)));
}
return traitsDict;
}
@end
#endif