Initial commit 🎉

fbshipit-source-id: b6fc29740c6875d2e78953b8a7123890a67930f2
Co-authored-by: Sebastian McKenzie <sebmck@fb.com>
Co-authored-by: John Knox <jknox@fb.com>
Co-authored-by: Emil Sjölander <emilsj@fb.com>
Co-authored-by: Pritesh Nandgaonkar <prit91@fb.com>
This commit is contained in:
Daniel Büchele
2018-04-13 08:38:06 -07:00
committed by Daniel Buchele
commit fbbf8cf16b
659 changed files with 87130 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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;
@interface SKDescriptorMapper : NSObject
- (instancetype)initWithDefaults;
- (SKNodeDescriptor *)descriptorForClass:(Class)cls;
- (void)registerDescriptor:(SKNodeDescriptor *)descriptor forClass:(Class)cls;
- (NSArray<SKNodeDescriptor *> *)allDescriptors;
@end

View File

@@ -0,0 +1,64 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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 "SKDescriptorMapper.h"
#import "SKApplicationDescriptor.h"
#import "SKButtonDescriptor.h"
#import "SKScrollViewDescriptor.h"
#import "SKViewControllerDescriptor.h"
#import "SKViewDescriptor.h"
@implementation SKDescriptorMapper
{
NSMutableDictionary<NSString *, SKNodeDescriptor *> *_descriptors;
}
- (instancetype)initWithDefaults {
if (self = [super init]) {
_descriptors = [NSMutableDictionary new];
[self registerDescriptor: [[SKApplicationDescriptor alloc] initWithDescriptorMapper: self]
forClass: [UIApplication class]];
[self registerDescriptor: [[SKViewControllerDescriptor alloc] initWithDescriptorMapper: self]
forClass: [UIViewController class]];
[self registerDescriptor: [[SKScrollViewDescriptor alloc] initWithDescriptorMapper: self]
forClass: [UIScrollView class]];
[self registerDescriptor: [[SKButtonDescriptor alloc] initWithDescriptorMapper: self]
forClass: [UIButton class]];
[self registerDescriptor: [[SKViewDescriptor alloc] initWithDescriptorMapper: self]
forClass: [UIView class]];
}
return self;
}
- (SKNodeDescriptor *)descriptorForClass:(Class)cls {
SKNodeDescriptor *classDescriptor = nil;
while (classDescriptor == nil && cls != nil) {
classDescriptor = [_descriptors objectForKey: NSStringFromClass(cls)];
cls = [cls superclass];
}
return classDescriptor;
}
- (void)registerDescriptor:(SKNodeDescriptor *)descriptor forClass:(Class)cls {
NSString *className = NSStringFromClass(cls);
_descriptors[className] = descriptor;
}
- (NSArray<SKNodeDescriptor *> *)allDescriptors {
return [_descriptors allValues];
}
@end
#endif

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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 SKHighlightOverlay : NSObject
+ (instancetype)sharedInstance;
+ (UIColor*)overlayColor;
- (void)mountInView:(UIView *)view withFrame:(CGRect)frame;
- (void)unmount;
@end

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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 "SKHighlightOverlay.h"
@implementation SKHighlightOverlay
{
CALayer *_overlayLayer;
}
+ (instancetype)sharedInstance {
static SKHighlightOverlay *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [self new];
});
return sharedInstance;
}
- (instancetype)init {
if (self = [super init]) {
_overlayLayer = [CALayer layer];
_overlayLayer.backgroundColor = [SKHighlightOverlay overlayColor].CGColor;
}
return self;
}
- (void)mountInView:(UIView *)view withFrame:(CGRect)frame {
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue
forKey:kCATransactionDisableActions];
_overlayLayer.frame = frame;
[view.layer addSublayer: _overlayLayer];
[CATransaction commit];
}
- (void)unmount {
[_overlayLayer removeFromSuperlayer];
}
+ (UIColor*)overlayColor {
return [UIColor colorWithRed:136.0 / 255.0 green:117.0 / 255.0 blue:197.0 / 255.0 alpha:0.6];
}
@end
#endif

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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,57 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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 "UIView+SKInvalidation.h"
#import "UICollectionView+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) 2018-present, Facebook, Inc.
*
* 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) 2018-present, Facebook, Inc.
*
* 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,115 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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 "SKDescriptorMapper.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:(SKDescriptorMapper *)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;
/**
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 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,83 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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"
@implementation SKNodeDescriptor
{
SKDescriptorMapper *_mapper;
}
- (void)setUp {
}
- (instancetype)initWithDescriptorMapper:(SKDescriptorMapper *)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 *)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<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];
return [self string:name contains:query] || [self string:[self identifierForNode: node] 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,52 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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,117 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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) 2004-present, Facebook, Inc.
*
* 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,50 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#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) 2018-present, Facebook, Inc.
*
* 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,12 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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,91 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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 "SKHighlightOverlay.h"
#import "SKHiddenWindow.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;
_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,28 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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 "SKDescriptorMapper.h"
typedef void (^SKTouchFinishDelegate)(NSArray<NSString *> *path);
@interface SKTouch : NSObject
- (instancetype)initWithTouchPoint:(CGPoint)touchPoint
withRootNode:(id<NSObject>)node
withDescriptorMapper:(SKDescriptorMapper *)mapper
finishWithBlock:(SKTouchFinishDelegate)d;
- (void)continueWithChildIndex:(NSUInteger)childIndex
withOffset:(CGPoint)offset;
- (void)finish;
- (BOOL)containedIn:(CGRect)bounds;
@end

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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;
NSMutableArray<NSString *> *_path;
CGPoint _currentTouchPoint;
id<NSObject> _currentNode;
SKDescriptorMapper *_descriptorMapper;
}
- (instancetype)initWithTouchPoint:(CGPoint)touchPoint
withRootNode:(id<NSObject>)node
withDescriptorMapper:(SKDescriptorMapper *)mapper
finishWithBlock:(SKTouchFinishDelegate)finishBlock {
if (self = [super init]) {
_onFinish = finishBlock;
_currentTouchPoint = touchPoint;
_currentNode = node;
_descriptorMapper = mapper;
_path = [NSMutableArray new];
}
return self;
}
- (void)continueWithChildIndex:(NSUInteger)childIndex withOffset:(CGPoint)offset {
_currentTouchPoint.x -= offset.x;
_currentTouchPoint.y -= offset.y;
SKNodeDescriptor *descriptor = [_descriptorMapper descriptorForClass: [_currentNode class]];
_currentNode = [descriptor childForNode: _currentNode atIndex: childIndex];
descriptor = [_descriptorMapper descriptorForClass: [_currentNode class]];
[_path addObject: [descriptor identifierForNode: _currentNode]];
[descriptor hitTest: self forNode: _currentNode];
}
- (void)finish {
_onFinish(_path);
}
- (BOOL)containedIn:(CGRect)bounds {
return CGRectContainsPoint(bounds, _currentTouchPoint);
}
@end
#endif

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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 <Foundation/Foundation.h>
#import <SonarKit/SonarPlugin.h>
#import "SKTapListener.h"
#import "SKInvalidation.h"
#import "SKDescriptorMapper.h"
@interface SonarKitLayoutPlugin : NSObject<SonarPlugin, SKInvalidationDelegate>
- (instancetype)initWithRootNode:(id<NSObject>)rootNode
withDescriptorMapper:(SKDescriptorMapper *)mapper;
- (instancetype)initWithRootNode:(id<NSObject>)rootNode
withTapListener:(id<SKTapListener>)tapListener
withDescriptorMapper:(SKDescriptorMapper *)mapper;
@property (nonatomic, readonly, strong) SKDescriptorMapper *descriptorMapper;
@end
#endif

View File

@@ -0,0 +1,414 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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 "SonarKitLayoutPlugin.h"
#import <SonarKit/SonarClient.h>
#import <SonarKit/SonarConnection.h>
#import <SonarKit/SonarResponder.h>
#import <SonarKit/SKMacros.h>
#import "SKDescriptorMapper.h"
#import "SKNodeDescriptor.h"
#import "SKTapListener.h"
#import "SKTapListenerImpl.h"
#import "SKSearchResultNode.h"
#import <mutex>
@implementation SonarKitLayoutPlugin
{
NSMapTable<NSString *, id> *_trackedObjects;
NSString *_lastHighlightedNode;
NSMutableSet *_invalidObjects;
Boolean _invalidateMessageQueued;
NSDate *_lastInvalidateMessage;
std::mutex invalidObjectsMutex;
id<NSObject> _rootNode;
id<SKTapListener> _tapListener;
id<SonarConnection> _connection;
NSMutableSet *_registeredDelegates;
}
- (instancetype)initWithRootNode:(id<NSObject>)rootNode
withDescriptorMapper:(SKDescriptorMapper *)mapper{
return [self initWithRootNode: rootNode
withTapListener: [SKTapListenerImpl new]
withDescriptorMapper: mapper];
}
- (instancetype)initWithRootNode:(id<NSObject>)rootNode
withTapListener:(id<SKTapListener>)tapListener
withDescriptorMapper:(SKDescriptorMapper *)mapper {
if (self = [super init]) {
_descriptorMapper = mapper;
_trackedObjects = [NSMapTable strongToWeakObjectsMapTable];
_lastHighlightedNode = nil;
_invalidObjects = [NSMutableSet new];
_invalidateMessageQueued = false;
_lastInvalidateMessage = [NSDate date];
_rootNode = rootNode;
_tapListener = tapListener;
_registeredDelegates = [NSMutableSet new];
[SKInvalidation sharedInstance].delegate = self;
}
return self;
}
- (NSString *)identifier
{
return @"Inspector";
}
- (void)didConnect:(id<SonarConnection>)connection {
_connection = connection;
[SKInvalidation enableInvalidations];
// Run setup logic for each descriptor
for (SKNodeDescriptor *descriptor in _descriptorMapper.allDescriptors) {
[descriptor setUp];
}
// In order to avoid a retain cycle (Connection -> Block -> SonarKitLayoutPlugin -> Connection ...)
__weak SonarKitLayoutPlugin *weakSelf = self;
[connection receive:@"getRoot" withBlock:^(NSDictionary *params, id<SonarResponder> responder) {
SonarPerformBlockOnMainThread(^{ [weakSelf onCallGetRoot: responder]; });
}];
[connection receive:@"getNodes" withBlock:^(NSDictionary *params, id<SonarResponder> responder) {
SonarPerformBlockOnMainThread(^{ [weakSelf onCallGetNodes: params[@"ids"] withResponder: responder]; });
}];
[connection receive:@"setData" withBlock:^(NSDictionary *params, id<SonarResponder> responder) {
SonarPerformBlockOnMainThread(^{
[weakSelf onCallSetData: params[@"id"]
withPath: params[@"path"]
toValue: params[@"value"]
withConnection: connection];
});
}];
[connection receive:@"setHighlighted" withBlock:^(NSDictionary *params, id<SonarResponder> responder) {
SonarPerformBlockOnMainThread(^{ [weakSelf onCallSetHighlighted: params[@"id"] withResponder: responder]; });
}];
[connection receive:@"setSearchActive" withBlock:^(NSDictionary *params, id<SonarResponder> responder) {
SonarPerformBlockOnMainThread(^{ [weakSelf onCallSetSearchActive: [params[@"active"] boolValue] withConnection: connection]; });
}];
[connection receive:@"isConsoleEnabled" withBlock:^(NSDictionary *params, id<SonarResponder> responder) {
SonarPerformBlockOnMainThread(^{ [responder success: @{@"isEnabled": @NO}];});
}];
[connection receive:@"getSearchResults" withBlock:^(NSDictionary *params, id<SonarResponder> responder) {
SonarPerformBlockOnMainThread(^{ [weakSelf onCallGetSearchResults: params[@"query"] withResponder: responder]; });
}];
}
- (void)didDisconnect {
// Clear the last highlight if there is any
[self onCallSetHighlighted: nil withResponder: nil];
// Disable search if it is active
[self onCallSetSearchActive: NO withConnection: nil];
}
- (void)onCallGetRoot:(id<SonarResponder>)responder {
const auto rootNode= [self getNode: [self trackObject: _rootNode]];
[responder success: rootNode];
}
- (void)onCallGetNodes:(NSArray<NSDictionary *> *)nodeIds withResponder:(id<SonarResponder>)responder {
NSMutableArray<NSDictionary *> *elements = [NSMutableArray new];
for (id nodeId in nodeIds) {
const auto node = [self getNode: nodeId];
if (node == nil) {
continue;
}
[elements addObject: node];
}
[responder success: @{ @"elements": elements }];
}
- (void)onCallSetData:(NSString *)objectId
withPath:(NSArray<NSString *> *)path
toValue:(id<NSObject>)value
withConnection:(id<SonarConnection>)connection {
id node = [_trackedObjects objectForKey: objectId];
if (node == nil) {
SKLog(@"node is nil, trying to setData: \
objectId: %@ \
path: %@ \
value: %@",
objectId, path, value);
return;
}
// Sonar sends nil/NSNull on some values when the text-field
// is empty, disregard these changes otherwise we'll crash.
if (value == nil || [value isKindOfClass: [NSNull class]]) {
return;
}
SKNodeDescriptor *descriptor = [_descriptorMapper descriptorForClass: [node class]];
NSString *dotJoinedPath = [path componentsJoinedByString: @"."];
SKNodeUpdateData updateDataForPath = [[descriptor dataMutationsForNode: node] objectForKey: dotJoinedPath];
if (updateDataForPath != nil) {
updateDataForPath(value);
[connection send: @"invalidate" withParams: @{ @"id": [descriptor identifierForNode: node] }];
}
}
- (void)onCallGetSearchResults:(NSString *)query withResponder:(id<SonarResponder>)responder {
const auto alreadyAddedElements = [NSMutableSet<NSString *> new];
SKSearchResultNode *matchTree = [self searchForQuery:(NSString *)[query lowercaseString] fromNode:(id)_rootNode withElementsAlreadyAdded: alreadyAddedElements];
[responder success: @{
@"results": [matchTree toNSDictionary] ?: [NSNull null],
@"query": query
}];
return;
}
- (void)onCallSetHighlighted:(NSString *)objectId withResponder:(id<SonarResponder>)responder {
if (_lastHighlightedNode != nil) {
id lastHighlightedObject = [_trackedObjects objectForKey: _lastHighlightedNode];
if (lastHighlightedObject == nil) {
[responder error: @{ @"error": @"unable to get last highlighted object" }];
return;
}
SKNodeDescriptor *descriptor = [self->_descriptorMapper descriptorForClass: [lastHighlightedObject class]];
[descriptor setHighlighted: NO forNode: lastHighlightedObject];
_lastHighlightedNode = nil;
}
if (objectId == nil || [objectId isKindOfClass:[NSNull class]]) {
return;
}
id object = [_trackedObjects objectForKey: objectId];
if (object == nil) {
SKLog(@"tried to setHighlighted for untracked id, objectId: %@", objectId);
return;
}
SKNodeDescriptor *descriptor = [self->_descriptorMapper descriptorForClass: [object class]];
[descriptor setHighlighted: YES forNode: object];
_lastHighlightedNode = objectId;
}
- (void)onCallSetSearchActive:(BOOL)active withConnection:(id<SonarConnection>)connection {
if (active) {
[_tapListener mountWithFrame: [[UIScreen mainScreen] bounds]];
__block id<NSObject> rootNode = _rootNode;
[_tapListener listenForTapWithBlock:^(CGPoint touchPoint) {
SKTouch *touch =
[[SKTouch alloc] initWithTouchPoint: touchPoint
withRootNode: rootNode
withDescriptorMapper: self->_descriptorMapper
finishWithBlock:^(NSArray<NSString *> *path) {
[connection send: @"select"
withParams: @{ @"path": path }];
}];
SKNodeDescriptor *descriptor = [self->_descriptorMapper descriptorForClass: [rootNode class]];
[descriptor hitTest: touch forNode: rootNode];
}];
} else {
[_tapListener unmount];
}
}
- (void)invalidateNode:(id<NSObject>)node {
SKNodeDescriptor *descriptor = [_descriptorMapper descriptorForClass: [node class]];
if (descriptor == nil) {
return;
}
NSString *nodeId = [descriptor identifierForNode: node];
if (![_trackedObjects objectForKey: nodeId]) {
return;
}
[descriptor invalidateNode: node];
// Collect invalidate messages before sending in a batch
std::lock_guard<std::mutex> lock(invalidObjectsMutex);
[_invalidObjects addObject:nodeId];
if (_invalidateMessageQueued) {
return;
}
_invalidateMessageQueued = true;
if (_lastInvalidateMessage.timeIntervalSinceNow < -1) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 500 * NSEC_PER_MSEC), dispatch_get_main_queue(), ^{
[self reportInvalidatedObjects];
});
}
}
- (void)reportInvalidatedObjects {
std::lock_guard<std::mutex> lock(invalidObjectsMutex);
NSMutableArray *nodes = [NSMutableArray new];
for (NSString *nodeId in self->_invalidObjects) {
[nodes addObject: [NSDictionary dictionaryWithObject: nodeId forKey: @"id"]];
}
[self->_connection send: @"invalidate" withParams: [NSDictionary dictionaryWithObject: nodes forKey: @"nodes"]];
self->_lastInvalidateMessage = [NSDate date];
self->_invalidObjects = [NSMutableSet new];
self->_invalidateMessageQueued = false;
return;
}
- (void)updateNodeReference:(id<NSObject>)node {
SKNodeDescriptor *descriptor = [_descriptorMapper descriptorForClass: [node class]];
if (descriptor == nil) {
return;
}
NSString *nodeId = [descriptor identifierForNode: node];
[_trackedObjects setObject:node forKey:nodeId];
}
- (SKSearchResultNode *)searchForQuery:(NSString *)query fromNode:(id)node withElementsAlreadyAdded:(NSMutableSet<NSString *> *)alreadyAdded {
SKNodeDescriptor *descriptor = [_descriptorMapper descriptorForClass: [node class]];
if (node == nil || descriptor == nil) {
return nil;
}
NSMutableArray<SKSearchResultNode *> *childTrees = nil;
BOOL isMatch = [descriptor matchesQuery: query forNode: node];
NSString *nodeId = [self trackObject: node];
for (auto i = 0; i < [descriptor childCountForNode: node]; i++) {
id child = [descriptor childForNode: node atIndex: i];
if (child) {
SKSearchResultNode *childTree = [self searchForQuery: query fromNode: child withElementsAlreadyAdded:alreadyAdded];
if (childTree != nil) {
if (childTrees == nil) {
childTrees = [NSMutableArray new];
}
[childTrees addObject: childTree];
}
}
}
if (isMatch || childTrees != nil) {
NSDictionary *element = [self getNode: nodeId];
if (nodeId == nil || element == nil) {
return nil;
}
NSMutableArray<NSString *> *descriptorChildElements = [element objectForKey: @"children"];
NSMutableDictionary *newElement = [element mutableCopy];
NSMutableArray<NSString *> *childElementsToReturn = [NSMutableArray new];
for (NSString *child in descriptorChildElements) {
if (![alreadyAdded containsObject: child]) {
[alreadyAdded addObject: child]; //todo add all at end
[childElementsToReturn addObject: child];
}
}
[newElement setObject: childElementsToReturn forKey: @"children"];
return [[SKSearchResultNode alloc] initWithNode: nodeId
asMatch: isMatch
withElement: newElement
andChildren: childTrees];
}
return nil;
}
- (NSDictionary *)getNode:(NSString *)nodeId {
id<NSObject> node = [_trackedObjects objectForKey: nodeId];
if (node == nil) {
SKLog(@"node is nil, no tracked node found for nodeId: %@", nodeId);
return nil;
}
SKNodeDescriptor *nodeDescriptor = [_descriptorMapper descriptorForClass: [node class]];
if (nodeDescriptor == nil) {
SKLog(@"No registered descriptor for class: %@", [node class]);
return nil;
}
NSMutableArray *attributes = [NSMutableArray new];
NSMutableDictionary *data = [NSMutableDictionary new];
const auto *nodeAttributes = [nodeDescriptor attributesForNode: node];
for (const SKNamed<NSString *> *namedPair in nodeAttributes) {
const auto name = namedPair.name;
if (name) {
const NSDictionary *attribute = @{
@"name": name,
@"value": namedPair.value ?: [NSNull null],
};
[attributes addObject: attribute];
}
}
const auto *nodeData = [nodeDescriptor dataForNode: node];
for (const SKNamed<NSDictionary *> *namedPair in nodeData) {
data[namedPair.name] = namedPair.value;
}
NSMutableArray *children = [NSMutableArray new];
for (NSUInteger i = 0; i < [nodeDescriptor childCountForNode: node]; i++) {
id childNode = [nodeDescriptor childForNode: node atIndex: i];
NSString *childIdentifier = [self trackObject: childNode];
if (childIdentifier) {
[children addObject: childIdentifier];
}
}
NSDictionary *nodeDic =
@{
// We shouldn't get nil for id/name/decoration, but let's not crash if we do.
@"id": [nodeDescriptor identifierForNode: node] ?: @"(unknown)",
@"name": [nodeDescriptor nameForNode: node] ?: @"(unknown)",
@"children": children,
@"attributes": attributes,
@"data": data,
@"decoration": [nodeDescriptor decorationForNode: node] ?: @"(unknown)",
};
return nodeDic;
}
- (NSString *)trackObject:(id)object {
const SKNodeDescriptor *descriptor = [_descriptorMapper descriptorForClass: [object class]];
NSString *objectIdentifier = [descriptor identifierForNode: object];
if (objectIdentifier == nil) {
return nil;
}
if (! [_trackedObjects objectForKey: objectIdentifier]) {
[_trackedObjects setObject:object forKey:objectIdentifier];
}
return objectIdentifier;
}
@end
#endif

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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 <SonarKit/SKMacros.h>
FB_LINK_REQUIRE(UICollectionView_SKInvalidation)
@interface UICollectionView (SKInvalidation)
+ (void)enableInvalidations;
@end

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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) 2018-present, Facebook, Inc.
*
* 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 <SonarKit/SKMacros.h>
#import "SKObject.h"
FB_LINK_REQUIRE(UIColor_SonarValueCoder)
@interface UIColor (SonarValueCoder) <SKSonarValueCoder>
@end

View File

@@ -0,0 +1,68 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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) 2018-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the LICENSE
* file in the root directory of this source tree.
*
*/
#import <SonarKit/SKMacros.h>
FB_LINK_REQUIRE(UIView_SKInvalidation)
@interface UIView (SKInvalidation)
+ (void)enableInvalidation;
@end

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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(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];
id<SKInvalidationDelegate> delegate = [SKInvalidation sharedInstance].delegate;
if (delegate != nil) {
[delegate invalidateNode: view];
}
}
- (void)swizzle_removeFromSuperview {
id<SKInvalidationDelegate> delegate = [SKInvalidation sharedInstance].delegate;
if (delegate != nil && self.superview != nil) {
[delegate invalidateNode: self.superview];
}
[self swizzle_removeFromSuperview];
}
@end
#endif

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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 "SKNodeDescriptor.h"
@interface SKApplicationDescriptor : SKNodeDescriptor<UIApplication *>
@end

View File

@@ -0,0 +1,66 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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 "SKDescriptorMapper.h"
#import "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 {
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];
return;
}
}
[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")]) {
continue;
}
[children addObject: window];
}
return children;
}
@end
#endif

View File

@@ -0,0 +1,16 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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 "SKNodeDescriptor.h"
@class UIButton;
@interface SKButtonDescriptor : SKNodeDescriptor<UIButton *>
@end

View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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 "SKDescriptorMapper.h"
#import "SKObject.h"
#import "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,14 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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 "SKNodeDescriptor.h"
@interface SKScrollViewDescriptor : SKNodeDescriptor<UIScrollView *>
@end

View File

@@ -0,0 +1,86 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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"
#import "SKDescriptorMapper.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 {
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];
return;
}
}
[touch finish];
}
@end
#endif

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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 "SKNodeDescriptor.h"
@interface SKViewControllerDescriptor : SKNodeDescriptor<UIViewController *>
@end

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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"
#import "SKDescriptorMapper.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,18 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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 "SKNodeDescriptor.h"
@interface SKViewDescriptor : SKNodeDescriptor<UIView *>
@end
#endif

View File

@@ -0,0 +1,575 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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 "SKDescriptorMapper.h"
#import "SKHighlightOverlay.h"
#import "SKNamed.h"
#import "SKObject.h"
#import "SKYogaKitHelper.h"
#import "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:(SKDescriptorMapper *)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 ?: @""),
@"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 {
return @{
// 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) {
node.accessibilityTraits = AccessibilityTraitsToggle(node.accessibilityTraits, UIAccessibilityTraitTabBar, [value boolValue]);
},
@"Accessibility.accessibilityViewIsModal": ^(NSNumber *value) {
node.accessibilityViewIsModal = [value boolValue];
},
@"Accessibility.shouldGroupAccessibilityChildren": ^(NSNumber *value) {
node.shouldGroupAccessibilityChildren = [value boolValue];
},
};
}
- (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 {
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) {
continue;
}
if ([touch containedIn: viewForNode.frame]) {
[touch continueWithChildIndex: index withOffset: viewForNode.frame.origin ];
return;
}
}
[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

View File

@@ -0,0 +1,11 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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) 2018-present, Facebook, Inc.
*
* 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) 2018-present, Facebook, Inc.
*
* 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) 2018-present, Facebook, Inc.
*
* 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,32 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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,31 @@
/*
* Copyright (c) 2018-present, Facebook, Inc.
*
* 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; \
}