diff --git a/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/FlipperKitLayoutComponentKitSupport.mm b/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/FlipperKitLayoutComponentKitSupport.mm index efa5ec03a..45970600a 100644 --- a/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/FlipperKitLayoutComponentKitSupport.mm +++ b/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/FlipperKitLayoutComponentKitSupport.mm @@ -16,6 +16,8 @@ #import "SKComponentLayoutDescriptor.h" #import "SKComponentLayoutWrapper.h" +#import "SKComponentMountedView.h" +#import "SKComponentMountedViewDescriptor.h" #import "SKComponentRootViewDescriptor.h" @implementation FlipperKitLayoutComponentKitSupport @@ -29,6 +31,9 @@ [mapper registerDescriptor:[[SKComponentLayoutDescriptor alloc] initWithDescriptorMapper:mapper] forClass:[SKComponentLayoutWrapper class]]; + [mapper registerDescriptor:[[SKComponentMountedViewDescriptor alloc] + initWithDescriptorMapper:mapper] + forClass:[SKComponentMountedView class]]; } @end diff --git a/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentLayoutDescriptor.mm b/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentLayoutDescriptor.mm index 0e0ccf54d..36425a5c0 100644 --- a/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentLayoutDescriptor.mm +++ b/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentLayoutDescriptor.mm @@ -29,6 +29,7 @@ #import "CKComponent+Sonar.h" #import "SKComponentLayoutWrapper.h" +#import "SKComponentMountedView.h" #import "SKSubDescriptor.h" #import "Utils.h" @@ -64,21 +65,26 @@ static std::vector>& subDescriptors() { } - (NSUInteger)childCountForNode:(SKComponentLayoutWrapper*)node { - NSUInteger count = node.children.size(); - if (count == 0) { - count = node.component.viewContext.view ? 1 : 0; + if (!node) { + return 0; // -children will return garbage if invoked on nil } - return count; + return node.children.match( + [](SKLeafViewChild) -> NSUInteger { return 1; }, + [](SKMountedViewChild) -> NSUInteger { return 1; }, + [](const std::vector& components) + -> NSUInteger { return components.size(); }); } - (id)childForNode:(SKComponentLayoutWrapper*)node atIndex:(NSUInteger)index { - if (node.children.size() == 0) { - if (node.rootNode == node.component.viewContext.view) { - return nil; - } - return node.component.viewContext.view; + if (!node) { + return nil; // -children will return garbage if invoked on nil } - return node.children[index]; + return node.children.match( + [](SKLeafViewChild leafView) -> id { return leafView.view; }, + [](SKMountedViewChild mountedView) -> id { return mountedView.view; }, + [&](const std::vector& components) -> id { + return components[index]; + }); } - (NSArray*>*>*)dataForNode: @@ -86,12 +92,11 @@ static std::vector>& subDescriptors() { NSMutableArray*>*>* data = [NSMutableArray new]; - if (node.isFlexboxChild) { - [data - addObject:[SKNamed - newWithName:@"Layout" - withValue:[self - propsForFlexboxChild:node.flexboxChild]]]; + if (node) { + node.flexboxChild.apply([&](const CKFlexboxComponentChild& child) { + [data addObject:[SKNamed newWithName:@"Layout" + withValue:[self propsForFlexboxChild:child]]]; + }); } NSMutableDictionary* extraData = [[NSMutableDictionary alloc] init]; @@ -163,29 +168,33 @@ static std::vector>& subDescriptors() { } - (void)hitTest:(SKTouch*)touch forNode:(SKComponentLayoutWrapper*)node { - if (node.children.size() == 0) { - UIView* componentView = node.component.viewContext.view; - if (componentView != nil) { - if ([touch containedIn:componentView.bounds]) { - [touch continueWithChildIndex:0 withOffset:componentView.bounds.origin]; - return; - } - } + if (!node) { + return; // -children will return garbage if invoked on nil } - - NSInteger index = 0; - for (index = node.children.size() - 1; index >= 0; index--) { - const auto child = node.children[index]; - - CGRect frame = {.origin = child.position, .size = child.size}; - - if ([touch containedIn:frame]) { - [touch continueWithChildIndex:index withOffset:child.position]; - return; - } + BOOL didContinueTouch = node.children.match( + [&](SKLeafViewChild leafView) -> BOOL { + [touch continueWithChildIndex:0 withOffset:{0, 0}]; + return YES; + }, + [&](SKMountedViewChild mountedView) -> BOOL { + [touch continueWithChildIndex:0 withOffset:{0, 0}]; + return YES; + }, + [&](std::vector children) -> BOOL { + for (auto it = children.rbegin(); it != children.rend(); ++it) { + SKComponentLayoutWrapper* wrapper = *it; + CGRect frame = {.origin = wrapper.position, .size = wrapper.size}; + if ([touch containedIn:frame]) { + NSUInteger index = std::distance(children.begin(), it.base()) - 1; + [touch continueWithChildIndex:index withOffset:wrapper.position]; + return YES; + } + } + return NO; + }); + if (!didContinueTouch) { + [touch finish]; } - - [touch finish]; } - (BOOL)matchesQuery:(NSString*)query forNode:(id)node { diff --git a/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentLayoutWrapper.h b/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentLayoutWrapper.h index 9a7ed2a2d..29b71bf9b 100644 --- a/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentLayoutWrapper.h +++ b/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentLayoutWrapper.h @@ -9,8 +9,35 @@ #import #import +#import +#import + +#import @protocol CKInspectableView; +@class SKComponentLayoutWrapper; +@class SKComponentMountedView; + +// CK::Variant does not support Objective-C types unless they are boxed: +struct SKLeafViewChild { + UIView* view; +}; +struct SKMountedViewChild { + SKComponentMountedView* view; +}; + +/** + The children of a SKComponentLayoutWrapper may be: + - A single leaf view, which may have UIView children of its own. + - A single non-leaf view, if the component created a view; its children will be + the component's child components. + - An array of SKComponentLayoutWrappers, if the component did not create a + view. + */ +using SKComponentLayoutWrapperChildren = CK::Variant< + SKLeafViewChild, + SKMountedViewChild, + std::vector>; @interface SKComponentLayoutWrapper : NSObject @@ -18,11 +45,11 @@ @property(nonatomic, readonly) NSString* identifier; @property(nonatomic, readonly) CGSize size; @property(nonatomic, readonly) CGPoint position; -@property(nonatomic, readonly) std::vector children; +@property(nonatomic, readonly) SKComponentLayoutWrapperChildren children; @property(nonatomic, weak, readonly) id rootNode; -// Null for layouts which are not direct children of a CKFlexboxComponent -@property(nonatomic, readonly) BOOL isFlexboxChild; -@property(nonatomic, readonly) CKFlexboxComponentChild flexboxChild; +/** CK::none for components that are not the child of a CKFlexboxComponent. */ +@property(nonatomic, readonly) CK::Optional + flexboxChild; + (instancetype)newFromRoot:(id)root parentKey:(NSString*)parentKey; diff --git a/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentLayoutWrapper.mm b/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentLayoutWrapper.mm index 84c16970c..a4ceae5fe 100644 --- a/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentLayoutWrapper.mm +++ b/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentLayoutWrapper.mm @@ -18,12 +18,13 @@ #import #import "CKComponent+Sonar.h" +#import "SKComponentMountedView.h" static char const kLayoutWrapperKey = ' '; -static CKFlexboxComponentChild findFlexboxLayoutParams( - CKComponent* parent, - CKComponent* child) { +static CK::Optional findFlexboxLayoutParams( + id parent, + id child) { if ([parent isKindOfClass:[CKFlexboxComponent class]]) { static Ivar ivar = class_getInstanceVariable([CKFlexboxComponent class], "_children"); @@ -42,7 +43,7 @@ static CKFlexboxComponentChild findFlexboxLayoutParams( } } - return {}; + return CK::none; } @implementation SKComponentLayoutWrapper @@ -66,6 +67,7 @@ static CKFlexboxComponentChild findFlexboxLayoutParams( SKComponentLayoutWrapper* const wrapper = [[SKComponentLayoutWrapper alloc] initWithLayout:layout position:CGPointMake(0, 0) + flexboxChild:CK::none parentKey:[NSString stringWithFormat:@"%@%d.", parentKey, @@ -85,6 +87,8 @@ static CKFlexboxComponentChild findFlexboxLayoutParams( - (instancetype)initWithLayout:(const CKComponentLayout&)layout position:(CGPoint)position + flexboxChild: + (CK::Optional)flexboxChild parentKey:(NSString*)parentKey reuseWrapper:(CKComponentReuseWrapper*)reuseWrapper rootNode:(id)node { @@ -93,6 +97,7 @@ static CKFlexboxComponentChild findFlexboxLayoutParams( _component = (CKComponent*)layout.component; _size = layout.size; _position = position; + _flexboxChild = flexboxChild; _identifier = [parentKey stringByAppendingString:layout.component ? layout.component.className : @"(null)"]; @@ -105,6 +110,7 @@ static CKFlexboxComponentChild findFlexboxLayoutParams( } } + std::vector childComponents; if (layout.children != nullptr) { int index = 0; for (const auto& child : *layout.children) { @@ -115,17 +121,26 @@ static CKFlexboxComponentChild findFlexboxLayoutParams( [[SKComponentLayoutWrapper alloc] initWithLayout:child.layout position:child.position + flexboxChild:findFlexboxLayoutParams( + _component, child.layout.component) parentKey:[_identifier stringByAppendingFormat:@"[%d].", index++] reuseWrapper:reuseWrapper rootNode:node]; - childWrapper->_isFlexboxChild = - [_component isKindOfClass:[CKFlexboxComponent class]]; - childWrapper->_flexboxChild = findFlexboxLayoutParams( - _component, (CKComponent*)child.layout.component); - _children.push_back(childWrapper); + childComponents.push_back(childWrapper); } } + + UIView* mountedView = _component.mountedView; + if (mountedView && !childComponents.empty()) { + _children = SKMountedViewChild{[[SKComponentMountedView alloc] + initWithView:mountedView + children:childComponents]}; + } else if (mountedView) { + _children = SKLeafViewChild{mountedView}; // leaf view + } else { + _children = childComponents; + } } return self; diff --git a/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentMountedView.h b/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentMountedView.h new file mode 100644 index 000000000..af9871b6a --- /dev/null +++ b/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentMountedView.h @@ -0,0 +1,35 @@ +/* + * 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 + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class SKComponentLayoutWrapper; + +/** + Represents a non-leaf view created by ComponentKit. Its corresponding + descriptor CKComponentMountedViewDescriptor delegates to the view's descriptor + for attributes and most other behaviors, but redirects back into ComponentKit's + SKComponentLayoutWrapper when queried for children. + + In this way, non-leaf views created by ComponentKit appear in the Flipper + layout hierarchy as the child of the component that created their view. + */ +@interface SKComponentMountedView : NSObject + +- (instancetype)initWithView:(UIView*)view + children:(std::vector)children; + +@property(nonatomic, readonly) UIView* view; +@property(nonatomic, readonly) std::vector children; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentMountedView.mm b/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentMountedView.mm new file mode 100644 index 000000000..eacbd8cef --- /dev/null +++ b/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentMountedView.mm @@ -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. + */ + +#if FB_SONARKIT_ENABLED + +#import "SKComponentMountedView.h" + +@implementation SKComponentMountedView + +- (instancetype)initWithView:(UIView*)view + children:(std::vector)children { + if (self = [super init]) { + _view = view; + _children = std::move(children); + } + return self; +} + +@end + +#endif diff --git a/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentMountedViewDescriptor.h b/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentMountedViewDescriptor.h new file mode 100644 index 000000000..b58f3b29d --- /dev/null +++ b/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentMountedViewDescriptor.h @@ -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 + +@class SKComponentMountedView; + +@interface SKComponentMountedViewDescriptor + : SKNodeDescriptor + +@end diff --git a/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentMountedViewDescriptor.mm b/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentMountedViewDescriptor.mm new file mode 100644 index 000000000..58f5fd22e --- /dev/null +++ b/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentMountedViewDescriptor.mm @@ -0,0 +1,93 @@ +/* + * 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 "SKComponentMountedViewDescriptor.h" + +#import +#import + +#import +#import +#import + +#import "CKComponent+Sonar.h" +#import "SKComponentLayoutWrapper.h" +#import "SKComponentMountedView.h" +#import "Utils.h" + +@implementation SKComponentMountedViewDescriptor + +- (SKNodeDescriptor*)_viewDescriptorFor:(SKComponentMountedView*)node { + // For most methods, we delegate to the descriptor for the underlying view. + return [self descriptorForClass:[node.view class]]; +} + +- (NSString*)identifierForNode:(SKComponentMountedView*)node { + return [[self _viewDescriptorFor:node] identifierForNode:node.view]; +} + +- (NSString*)nameForNode:(SKComponentMountedView*)node { + return [[self _viewDescriptorFor:node] nameForNode:node.view]; +} + +- (NSUInteger)childCountForNode:(SKComponentMountedView*)node { + // An obvious future improvement: we should also return any + // non-ComponentKit-managed child views of our view. + // Explicit nil check; -children will return garbage if invoked on nil + return node ? node.children.size() : 0; +} + +- (id)childForNode:(SKComponentMountedView*)node atIndex:(NSUInteger)index { + // Explicit nil check; -children will return garbage if invoked on nil + return node ? node.children[index] : nil; +} + +- (NSArray*>*>*)dataForNode: + (SKComponentMountedView*)node { + return [[self _viewDescriptorFor:node] dataForNode:node.view]; +} + +- (NSDictionary*)dataMutationsForNode: + (SKComponentMountedView*)node { + return [[self _viewDescriptorFor:node] dataMutationsForNode:node.view]; +} + +- (NSArray*>*)attributesForNode: + (SKComponentMountedView*)node { + return [[self _viewDescriptorFor:node] attributesForNode:node.view]; +} + +- (void)setHighlighted:(BOOL)highlighted forNode:(SKComponentMountedView*)node { + [[self _viewDescriptorFor:node] setHighlighted:highlighted forNode:node.view]; +} + +- (void)hitTest:(SKTouch*)touch forNode:(SKComponentMountedView*)node { + if (!node) { + return; // -children will return garbage if invoked on nil + } + const auto& children = node.children; + for (auto it = children.rbegin(); it != children.rend(); ++it) { + SKComponentLayoutWrapper* child = *it; + CGRect frame = {.origin = child.position, .size = child.size}; + if ([touch containedIn:frame]) { + NSUInteger index = std::distance(children.begin(), it.base()) - 1; + [touch continueWithChildIndex:index withOffset:child.position]; + return; + } + } + [touch finish]; +} + +- (BOOL)matchesQuery:(NSString*)query forNode:(SKComponentMountedView*)node { + return [[self _viewDescriptorFor:node] matchesQuery:query forNode:node.view]; +} + +@end + +#endif