Skip to content
This repository was archived by the owner on Sep 26, 2025. It is now read-only.

Commit d0803ec

Browse files
adamjernstfacebook-github-bot
authored andcommitted
Show mounted ComponentKit views in Flipper
Summary: Before this diff, Flipper showed *leaf* views created by ComponentKit, but not any intermediate views. Now we show both. A new node type `SKComponentMountedView` is used for this purpose. Its descriptor `SKComponentMountedViewDescriptor` mostly delegates to its view's descriptor, but redirects back into ComponentKit for children. Reviewed By: Andrey-Mishanin Differential Revision: D21130997 fbshipit-source-id: b3c12ea7cc1200962b3ba7c269c48d68b1809948
1 parent 756987e commit d0803ec

File tree

8 files changed

+274
-50
lines changed

8 files changed

+274
-50
lines changed

iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/FlipperKitLayoutComponentKitSupport.mm

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
#import "SKComponentLayoutDescriptor.h"
1818
#import "SKComponentLayoutWrapper.h"
19+
#import "SKComponentMountedView.h"
20+
#import "SKComponentMountedViewDescriptor.h"
1921
#import "SKComponentRootViewDescriptor.h"
2022

2123
@implementation FlipperKitLayoutComponentKitSupport
@@ -29,6 +31,9 @@ + (void)setUpWithDescriptorMapper:(SKDescriptorMapper*)mapper {
2931
[mapper registerDescriptor:[[SKComponentLayoutDescriptor alloc]
3032
initWithDescriptorMapper:mapper]
3133
forClass:[SKComponentLayoutWrapper class]];
34+
[mapper registerDescriptor:[[SKComponentMountedViewDescriptor alloc]
35+
initWithDescriptorMapper:mapper]
36+
forClass:[SKComponentMountedView class]];
3237
}
3338

3439
@end

iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentLayoutDescriptor.mm

Lines changed: 46 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
#import "CKComponent+Sonar.h"
3131
#import "SKComponentLayoutWrapper.h"
32+
#import "SKComponentMountedView.h"
3233
#import "SKSubDescriptor.h"
3334
#import "Utils.h"
3435

@@ -64,34 +65,38 @@ - (NSString*)decorationForNode:(SKComponentLayoutWrapper*)node {
6465
}
6566

6667
- (NSUInteger)childCountForNode:(SKComponentLayoutWrapper*)node {
67-
NSUInteger count = node.children.size();
68-
if (count == 0) {
69-
count = node.component.viewContext.view ? 1 : 0;
68+
if (!node) {
69+
return 0; // -children will return garbage if invoked on nil
7070
}
71-
return count;
71+
return node.children.match(
72+
[](SKLeafViewChild) -> NSUInteger { return 1; },
73+
[](SKMountedViewChild) -> NSUInteger { return 1; },
74+
[](const std::vector<SKComponentLayoutWrapper*>& components)
75+
-> NSUInteger { return components.size(); });
7276
}
7377

7478
- (id)childForNode:(SKComponentLayoutWrapper*)node atIndex:(NSUInteger)index {
75-
if (node.children.size() == 0) {
76-
if (node.rootNode == node.component.viewContext.view) {
77-
return nil;
78-
}
79-
return node.component.viewContext.view;
79+
if (!node) {
80+
return nil; // -children will return garbage if invoked on nil
8081
}
81-
return node.children[index];
82+
return node.children.match(
83+
[](SKLeafViewChild leafView) -> id { return leafView.view; },
84+
[](SKMountedViewChild mountedView) -> id { return mountedView.view; },
85+
[&](const std::vector<SKComponentLayoutWrapper*>& components) -> id {
86+
return components[index];
87+
});
8288
}
8389

8490
- (NSArray<SKNamed<NSDictionary<NSString*, NSObject*>*>*>*)dataForNode:
8591
(SKComponentLayoutWrapper*)node {
8692
NSMutableArray<SKNamed<NSDictionary<NSString*, NSObject*>*>*>* data =
8793
[NSMutableArray new];
8894

89-
if (node.isFlexboxChild) {
90-
[data
91-
addObject:[SKNamed
92-
newWithName:@"Layout"
93-
withValue:[self
94-
propsForFlexboxChild:node.flexboxChild]]];
95+
if (node) {
96+
node.flexboxChild.apply([&](const CKFlexboxComponentChild& child) {
97+
[data addObject:[SKNamed newWithName:@"Layout"
98+
withValue:[self propsForFlexboxChild:child]]];
99+
});
95100
}
96101
NSMutableDictionary<NSString*, NSObject*>* extraData =
97102
[[NSMutableDictionary alloc] init];
@@ -163,29 +168,33 @@ - (void)setHighlighted:(BOOL)highlighted
163168
}
164169

165170
- (void)hitTest:(SKTouch*)touch forNode:(SKComponentLayoutWrapper*)node {
166-
if (node.children.size() == 0) {
167-
UIView* componentView = node.component.viewContext.view;
168-
if (componentView != nil) {
169-
if ([touch containedIn:componentView.bounds]) {
170-
[touch continueWithChildIndex:0 withOffset:componentView.bounds.origin];
171-
return;
172-
}
173-
}
171+
if (!node) {
172+
return; // -children will return garbage if invoked on nil
174173
}
175-
176-
NSInteger index = 0;
177-
for (index = node.children.size() - 1; index >= 0; index--) {
178-
const auto child = node.children[index];
179-
180-
CGRect frame = {.origin = child.position, .size = child.size};
181-
182-
if ([touch containedIn:frame]) {
183-
[touch continueWithChildIndex:index withOffset:child.position];
184-
return;
185-
}
174+
BOOL didContinueTouch = node.children.match(
175+
[&](SKLeafViewChild leafView) -> BOOL {
176+
[touch continueWithChildIndex:0 withOffset:{0, 0}];
177+
return YES;
178+
},
179+
[&](SKMountedViewChild mountedView) -> BOOL {
180+
[touch continueWithChildIndex:0 withOffset:{0, 0}];
181+
return YES;
182+
},
183+
[&](std::vector<SKComponentLayoutWrapper*> children) -> BOOL {
184+
for (auto it = children.rbegin(); it != children.rend(); ++it) {
185+
SKComponentLayoutWrapper* wrapper = *it;
186+
CGRect frame = {.origin = wrapper.position, .size = wrapper.size};
187+
if ([touch containedIn:frame]) {
188+
NSUInteger index = std::distance(children.begin(), it.base()) - 1;
189+
[touch continueWithChildIndex:index withOffset:wrapper.position];
190+
return YES;
191+
}
192+
}
193+
return NO;
194+
});
195+
if (!didContinueTouch) {
196+
[touch finish];
186197
}
187-
188-
[touch finish];
189198
}
190199

191200
- (BOOL)matchesQuery:(NSString*)query forNode:(id)node {

iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentLayoutWrapper.h

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,47 @@
99

1010
#import <ComponentKit/CKComponentLayout.h>
1111
#import <ComponentKit/CKFlexboxComponent.h>
12+
#import <ComponentKit/CKOptional.h>
13+
#import <ComponentKit/CKVariant.h>
14+
15+
#import <vector>
1216

1317
@protocol CKInspectableView;
18+
@class SKComponentLayoutWrapper;
19+
@class SKComponentMountedView;
20+
21+
// CK::Variant does not support Objective-C types unless they are boxed:
22+
struct SKLeafViewChild {
23+
UIView* view;
24+
};
25+
struct SKMountedViewChild {
26+
SKComponentMountedView* view;
27+
};
28+
29+
/**
30+
The children of a SKComponentLayoutWrapper may be:
31+
- A single leaf view, which may have UIView children of its own.
32+
- A single non-leaf view, if the component created a view; its children will be
33+
the component's child components.
34+
- An array of SKComponentLayoutWrappers, if the component did not create a
35+
view.
36+
*/
37+
using SKComponentLayoutWrapperChildren = CK::Variant<
38+
SKLeafViewChild,
39+
SKMountedViewChild,
40+
std::vector<SKComponentLayoutWrapper*>>;
1441

1542
@interface SKComponentLayoutWrapper : NSObject
1643

1744
@property(nonatomic, weak, readonly) CKComponent* component;
1845
@property(nonatomic, readonly) NSString* identifier;
1946
@property(nonatomic, readonly) CGSize size;
2047
@property(nonatomic, readonly) CGPoint position;
21-
@property(nonatomic, readonly) std::vector<SKComponentLayoutWrapper*> children;
48+
@property(nonatomic, readonly) SKComponentLayoutWrapperChildren children;
2249
@property(nonatomic, weak, readonly) id<CKInspectableView> rootNode;
23-
// Null for layouts which are not direct children of a CKFlexboxComponent
24-
@property(nonatomic, readonly) BOOL isFlexboxChild;
25-
@property(nonatomic, readonly) CKFlexboxComponentChild flexboxChild;
50+
/** CK::none for components that are not the child of a CKFlexboxComponent. */
51+
@property(nonatomic, readonly) CK::Optional<CKFlexboxComponentChild>
52+
flexboxChild;
2653

2754
+ (instancetype)newFromRoot:(id<CKInspectableView>)root
2855
parentKey:(NSString*)parentKey;

iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/SKComponentLayoutWrapper.mm

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@
1818
#import <ComponentKit/CKInspectableView.h>
1919

2020
#import "CKComponent+Sonar.h"
21+
#import "SKComponentMountedView.h"
2122

2223
static char const kLayoutWrapperKey = ' ';
2324

24-
static CKFlexboxComponentChild findFlexboxLayoutParams(
25-
CKComponent* parent,
26-
CKComponent* child) {
25+
static CK::Optional<CKFlexboxComponentChild> findFlexboxLayoutParams(
26+
id<CKMountable> parent,
27+
id<CKMountable> child) {
2728
if ([parent isKindOfClass:[CKFlexboxComponent class]]) {
2829
static Ivar ivar =
2930
class_getInstanceVariable([CKFlexboxComponent class], "_children");
@@ -42,7 +43,7 @@ static CKFlexboxComponentChild findFlexboxLayoutParams(
4243
}
4344
}
4445

45-
return {};
46+
return CK::none;
4647
}
4748

4849
@implementation SKComponentLayoutWrapper
@@ -66,6 +67,7 @@ + (instancetype)newFromRoot:(id<CKInspectableView>)root
6667
SKComponentLayoutWrapper* const wrapper = [[SKComponentLayoutWrapper alloc]
6768
initWithLayout:layout
6869
position:CGPointMake(0, 0)
70+
flexboxChild:CK::none
6971
parentKey:[NSString
7072
stringWithFormat:@"%@%d.",
7173
parentKey,
@@ -85,6 +87,8 @@ + (instancetype)newFromRoot:(id<CKInspectableView>)root
8587

8688
- (instancetype)initWithLayout:(const CKComponentLayout&)layout
8789
position:(CGPoint)position
90+
flexboxChild:
91+
(CK::Optional<CKFlexboxComponentChild>)flexboxChild
8892
parentKey:(NSString*)parentKey
8993
reuseWrapper:(CKComponentReuseWrapper*)reuseWrapper
9094
rootNode:(id<CKInspectableView>)node {
@@ -93,6 +97,7 @@ - (instancetype)initWithLayout:(const CKComponentLayout&)layout
9397
_component = (CKComponent*)layout.component;
9498
_size = layout.size;
9599
_position = position;
100+
_flexboxChild = flexboxChild;
96101
_identifier = [parentKey stringByAppendingString:layout.component
97102
? layout.component.className
98103
: @"(null)"];
@@ -105,6 +110,7 @@ - (instancetype)initWithLayout:(const CKComponentLayout&)layout
105110
}
106111
}
107112

113+
std::vector<SKComponentLayoutWrapper*> childComponents;
108114
if (layout.children != nullptr) {
109115
int index = 0;
110116
for (const auto& child : *layout.children) {
@@ -115,17 +121,26 @@ - (instancetype)initWithLayout:(const CKComponentLayout&)layout
115121
[[SKComponentLayoutWrapper alloc]
116122
initWithLayout:child.layout
117123
position:child.position
124+
flexboxChild:findFlexboxLayoutParams(
125+
_component, child.layout.component)
118126
parentKey:[_identifier
119127
stringByAppendingFormat:@"[%d].", index++]
120128
reuseWrapper:reuseWrapper
121129
rootNode:node];
122-
childWrapper->_isFlexboxChild =
123-
[_component isKindOfClass:[CKFlexboxComponent class]];
124-
childWrapper->_flexboxChild = findFlexboxLayoutParams(
125-
_component, (CKComponent*)child.layout.component);
126-
_children.push_back(childWrapper);
130+
childComponents.push_back(childWrapper);
127131
}
128132
}
133+
134+
UIView* mountedView = _component.mountedView;
135+
if (mountedView && !childComponents.empty()) {
136+
_children = SKMountedViewChild{[[SKComponentMountedView alloc]
137+
initWithView:mountedView
138+
children:childComponents]};
139+
} else if (mountedView) {
140+
_children = SKLeafViewChild{mountedView}; // leaf view
141+
} else {
142+
_children = childComponents;
143+
}
129144
}
130145

131146
return self;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import <Foundation/Foundation.h>
9+
10+
#import <vector>
11+
12+
NS_ASSUME_NONNULL_BEGIN
13+
14+
@class SKComponentLayoutWrapper;
15+
16+
/**
17+
Represents a non-leaf view created by ComponentKit. Its corresponding
18+
descriptor CKComponentMountedViewDescriptor delegates to the view's descriptor
19+
for attributes and most other behaviors, but redirects back into ComponentKit's
20+
SKComponentLayoutWrapper when queried for children.
21+
22+
In this way, non-leaf views created by ComponentKit appear in the Flipper
23+
layout hierarchy as the child of the component that created their view.
24+
*/
25+
@interface SKComponentMountedView : NSObject
26+
27+
- (instancetype)initWithView:(UIView*)view
28+
children:(std::vector<SKComponentLayoutWrapper*>)children;
29+
30+
@property(nonatomic, readonly) UIView* view;
31+
@property(nonatomic, readonly) std::vector<SKComponentLayoutWrapper*> children;
32+
33+
@end
34+
35+
NS_ASSUME_NONNULL_END
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#if FB_SONARKIT_ENABLED
9+
10+
#import "SKComponentMountedView.h"
11+
12+
@implementation SKComponentMountedView
13+
14+
- (instancetype)initWithView:(UIView*)view
15+
children:(std::vector<SKComponentLayoutWrapper*>)children {
16+
if (self = [super init]) {
17+
_view = view;
18+
_children = std::move(children);
19+
}
20+
return self;
21+
}
22+
23+
@end
24+
25+
#endif
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import <FlipperKitLayoutPlugin/SKNodeDescriptor.h>
9+
10+
@class SKComponentMountedView;
11+
12+
@interface SKComponentMountedViewDescriptor
13+
: SKNodeDescriptor<SKComponentMountedView*>
14+
15+
@end

0 commit comments

Comments
 (0)