ComponentKit/StatefulViews/CKStatefulViewComponentController.mm (124 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 "CKStatefulViewComponentController.h"
#import <ComponentKit/CKComponent.h>
#import <ComponentKit/CKComponentInternal.h>
#import "CKStatefulViewComponent.h"
#import "CKStatefulViewReusePool.h"
#import "CKInternalHelpers.h"
@implementation CKStatefulViewComponentController
{
UIView *_statefulView;
BOOL _mounted;
id _statefulViewContext;
}
+ (UIView *)newStatefulView:(id)context
{
RCFailAssertWithCategory(NSStringFromClass(self), @"Should be implemented by subclasses.");
return nil;
}
+ (id)contextForNewStatefulView:(CKComponent *)component
{
return nil;
}
+ (void)configureStatefulView:(UIView *)statefulView
forComponent:(CKComponent *)component
{
RCFailAssertWithCategory(NSStringFromClass(self), @"Should be implemented by subclasses.");
}
+ (NSInteger)maximumPoolSize:(id)context
{
return -1;
}
+ (BOOL)isContextValid:(id)context {
return YES;
}
- (UIView *)statefulView
{
return _statefulView;
}
- (void)didAcquireStatefulView:(UIView *)statefulView {}
- (void)willRelinquishStatefulView:(UIView *)statefulView {}
- (BOOL)canRelinquishStatefulView
{
return YES;
}
- (void)canRelinquishStatefulViewDidChange
{
[self _relinquishStatefulViewIfPossible];
}
#pragma mark - Lifecycle
- (void)invalidateController
{
[super invalidateController];
[self _relinquishStatefulViewIfPossible];
}
- (void)didMount
{
[super didMount];
RCAssertWithCategory([[self component] isKindOfClass:[CKStatefulViewComponent class]], self.component.className, @"Component should be a stateful view component.");
RCAssertWithCategory(
!CKSubclassOverridesInstanceMethod([CKStatefulViewComponentController class], [self class], @selector(statefulView)),
self.component.className,
@"Should not override the method -statefulView.");
if (_statefulView == nil) {
_statefulViewContext = [[self class] contextForNewStatefulView:[self component]];
_statefulView = [[CKStatefulViewReusePool sharedPool] dequeueStatefulViewForControllerClass:[self class]
preferredSuperview:[self view]
context:_statefulViewContext];
if (_statefulView == nil) {
_statefulView = [[self class] newStatefulView:_statefulViewContext];
}
[[self class] configureStatefulView:_statefulView forComponent:[self component]];
[self didAcquireStatefulView:_statefulView];
}
[self _presentStatefulView];
_mounted = YES;
}
- (void)didRemount
{
[super didRemount];
[self _presentStatefulView];
}
- (void)didUpdateComponent
{
[super didUpdateComponent];
if (_statefulView) {
[[self class] configureStatefulView:_statefulView forComponent:[self component]];
}
}
- (void)didUnmount
{
[super didUnmount];
_mounted = NO;
[self _relinquishStatefulViewIfPossible];
}
#pragma mark - Helpers
- (void)_presentStatefulView
{
const CKComponentViewContext &context = [[self component] viewContext];
[_statefulView setFrame:context.frame];
RCAssertWithCategory([context.view.subviews count] <= 1, self.component.className, @"Should never have more than a single stateful subview.");
UIView *existingView = [context.view.subviews lastObject];
if (existingView != _statefulView) {
[existingView removeFromSuperview];
}
[context.view addSubview:_statefulView];
}
- (void)_relinquishStatefulViewIfPossible
{
if (_statefulView && [self canRelinquishStatefulView]) {
[[CKStatefulViewReusePool sharedPool]
enqueueStatefulView:_statefulView
forControllerClass:[self class]
context:_statefulViewContext
mayRelinquishBlock:^BOOL{
if (self->_statefulView && !self->_mounted && [self canRelinquishStatefulView]) {
[self willRelinquishStatefulView:self->_statefulView];
self->_statefulView = nil;
self->_statefulViewContext = nil;
return YES;
}
return NO;
}];
}
}
@end