ComponentKit/Core/Render/CKRenderComponent.mm (153 lines of code) (raw):
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "CKRenderComponent.h"
#import <ComponentKit/CKInternalHelpers.h>
#import <ComponentKit/CKMutex.h>
#import <RenderCore/RCComputeRootLayout.h>
#import "CKComponentInternal.h"
#import "CKComponentCreationValidation.h"
#import "CKComponentSubclass.h"
#import "CKComponent+LayoutLifecycle.h"
#import "CKThreadLocalComponentScope.h"
#import "CKIterableHelpers.h"
#import "CKRenderHelpers.h"
#import "CKTreeNode.h"
#import "ComponentLayoutContext.h"
@implementation CKRenderComponent
{
CKComponent *_child;
}
#if DEBUG
+ (void)initialize
{
if (self != [CKRenderComponent class]) {
RCAssert(!CKSubclassOverridesInstanceMethod([CKRenderComponent class], self, @selector(computeLayoutThatFits:)),
@"%@ overrides -computeLayoutThatFits: which is not allowed. "
"Consider subclassing CKLayoutComponent directly if you need to perform custom layout.",
self);
RCAssert(!CKSubclassOverridesInstanceMethod([CKRenderComponent class], self, @selector(layoutThatFits:parentSize:)),
@"%@ overrides -layoutThatFits:parentSize: which is not allowed. "
"Consider subclassing CKLayoutComponent directly if you need to perform custom layout.",
self);
}
}
#endif
- (void)didFinishComponentInitialization
{
// Not calling super intentionally.
CKValidateRenderComponentCreation();
CKThreadLocalComponentScope::markCurrentScopeWithRenderComponentInTree();
CKComponentContextHelper::didCreateRenderComponent(self);
}
- (CKComponent *)render:(id)state
{
RCFailAssert(@"%@ MUST override the '%@' method.", self.className, NSStringFromSelector(_cmd));
return nil;
}
- (UIView *)viewForAnimation
{
// Delegate to the wrapped component's viewForAnimation if we don't have one.
return [super viewForAnimation] ?: [_child viewForAnimation];
}
- (void)buildComponentTree:(CKTreeNode *)parent
previousParent:(CKTreeNode *_Nullable)previousParent
params:(const CKBuildComponentTreeParams &)params
parentHasStateUpdate:(BOOL)parentHasStateUpdate
{
// Build the component tree.
auto const node = CKRender::ComponentTree::Render::build(self, &_child, parent, previousParent, params, parentHasStateUpdate, nil);
auto const viewConfiguration = [self viewConfigurationWithState:node.state];
if (!viewConfiguration.isDefaultConfiguration()) {
[self setViewConfiguration:viewConfiguration];
}
}
- (RCLayout)computeLayoutThatFits:(CKSizeRange)constrainedSize
restrictedToSize:(const RCComponentSize &)size
relativeToParentSize:(CGSize)parentSize
{
RCAssert(size == RCComponentSize(),
@"CKRenderComponent only passes size {} to the super class initializer, but received size %@ "
"(component=%@)", size.description(), _child);
if (_child) {
RCLayout l;
if (CKReadGlobalConfig().enableLayoutCaching) {
#if CK_ASSERTIONS_ENABLED
const CKComponentContext<CKComponentCreationValidationContext> validationContext([[CKComponentCreationValidationContext alloc] initWithSource:CKComponentCreationValidationSourceLayout]);
#endif
CK::Component::LayoutContext context(self, constrainedSize);
auto const systraceListener = context.systraceListener;
CKComponentWillLayout(_child, constrainedSize, parentSize, systraceListener);
l = RCFetchOrComputeLayout(_child, constrainedSize, parentSize, &computeLayoutForModel);
CKComponentDidLayout(_child, l, constrainedSize, parentSize, systraceListener);
} else {
l = [_child layoutThatFits:constrainedSize parentSize:parentSize];
}
return {self, l.size, {{{0,0}, l}}};
}
return [super computeLayoutThatFits:constrainedSize restrictedToSize:size relativeToParentSize:parentSize];
}
static RCLayout computeLayoutForModel(id<CKMountable> model, const CKSizeRange &constrainedSize, CGSize parentSize)
{
const auto component = (CKComponent *)model;
return [component computeLayoutThatFits:constrainedSize restrictedToSize:component.size relativeToParentSize:parentSize];
}
- (CKComponent *)child
{
return _child;
}
- (unsigned int)numberOfChildren
{
return RCIterable::numberOfChildren(_child);
}
- (id<CKMountable>)childAtIndex:(unsigned int)index
{
return RCIterable::childAtIndex(self, index, _child);
}
+ (id)initialState
{
return CKTreeNodeEmptyState();
}
#pragma mark - CKRenderComponentProtocol
- (id)initialState
{
return [self.class initialState];
}
- (BOOL)shouldComponentUpdate:(id<CKReusableComponentProtocol>)component
{
return YES;
}
- (void)didReuseComponent:(id<CKRenderComponentProtocol>)component {}
- (CKComponentViewConfiguration)viewConfigurationWithState:(id)state
{
return {};
}
- (id _Nullable)componentIdentifier
{
return nil;
}
- (BOOL)requiresScopeHandle
{
if ([self.class controllerClass] != nil) {
return YES;
}
const Class componentClass = self.class;
static CK::StaticMutex mutex = CK_MUTEX_INITIALIZER; // protects cache
CK::StaticMutexLocker l(mutex);
static std::unordered_map<Class, BOOL> *cache = new std::unordered_map<Class, BOOL>();
auto it = cache->find(componentClass);
if (it == cache->end()) {
const BOOL requiresScopeHandle =
CKSubclassOverridesInstanceMethod([CKRenderComponent class], componentClass, @selector(buildController)) ||
CKSubclassOverridesInstanceMethod([CKRenderComponent class], componentClass, @selector(animationsFromPreviousComponent:)) ||
CKSubclassOverridesInstanceMethod([CKRenderComponent class], componentClass, @selector(animationsOnInitialMount)) ||
CKSubclassOverridesInstanceMethod([CKRenderComponent class], componentClass, @selector(animationsOnFinalUnmount));
it = cache->insert({componentClass, requiresScopeHandle}).first;
}
const BOOL requiresScopeHandle = it->second;
RCAssert(requiresScopeHandle ||
(!self.hasAnimations && !self.hasInitialMountAnimations && !self.hasFinalUnmountAnimations),
@"%@ changes the default logic of -has*Animations properties; Make sure to override -requiresScopeHandle "
"and return YES when animations are present.",
self);
return requiresScopeHandle;
}
- (instancetype)clone
{
// The default implementation returns `nil`, which indicates `clone` is not supported in this component.
return nil;
}
@end