Introducing RCTSurfaceHostingComponent
Summary: RCTSurfaceHostingComponent is ComponentKit component represents given Surface instance. Differential Revision: D6217104 fbshipit-source-id: 50805d97e744de24a188bc97b33de4709e785aae
This commit is contained in:
parent
6d92046c56
commit
e75bd87a76
|
@ -0,0 +1,22 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-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 <RCTSurfaceHostingComponent/RCTSurfaceHostingComponent.h>
|
||||||
|
#import <RCTSurfaceHostingComponent/RCTSurfaceHostingComponentOptions.h>
|
||||||
|
|
||||||
|
@class RCTSurface;
|
||||||
|
@class RCTSurfaceHostingComponentState;
|
||||||
|
|
||||||
|
@interface RCTSurfaceHostingComponent ()
|
||||||
|
|
||||||
|
@property (nonatomic, strong, readonly) RCTSurface *surface;
|
||||||
|
@property (nonatomic, retain, readonly) RCTSurfaceHostingComponentState *state;
|
||||||
|
@property (nonatomic, assign, readonly) RCTSurfaceHostingComponentOptions options;
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,22 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-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 <ComponentKit/CKComponent.h>
|
||||||
|
#import <RCTSurfaceHostingComponent/RCTSurfaceHostingComponentOptions.h>
|
||||||
|
|
||||||
|
@class RCTSurface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ComponentKit component represents given Surface instance.
|
||||||
|
*/
|
||||||
|
@interface RCTSurfaceHostingComponent : CKComponent
|
||||||
|
|
||||||
|
+ (instancetype)newWithSurface:(RCTSurface *)surface options:(RCTSurfaceHostingComponentOptions)options;
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,104 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-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 "RCTSurfaceHostingComponent.h"
|
||||||
|
#import "RCTSurfaceHostingComponent+Internal.h"
|
||||||
|
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
#import <ComponentKit/CKComponentSubclass.h>
|
||||||
|
#import <React/RCTSurface.h>
|
||||||
|
#import <React/RCTSurfaceView.h>
|
||||||
|
|
||||||
|
#import "RCTSurfaceHostingComponentState.h"
|
||||||
|
|
||||||
|
@implementation RCTSurfaceHostingComponent
|
||||||
|
|
||||||
|
+ (id)initialState
|
||||||
|
{
|
||||||
|
return [RCTSurfaceHostingComponentState new];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (instancetype)newWithSurface:(RCTSurface *)surface options:(RCTSurfaceHostingComponentOptions)options
|
||||||
|
{
|
||||||
|
CKComponentScope scope(self, surface);
|
||||||
|
|
||||||
|
RCTSurfaceHostingComponentState *const state = scope.state();
|
||||||
|
|
||||||
|
RCTSurfaceHostingComponentState *const newState =
|
||||||
|
[RCTSurfaceHostingComponentState newWithStage:surface.stage
|
||||||
|
intrinsicSize:surface.intrinsicSize];
|
||||||
|
|
||||||
|
if (![state isEqual:newState]) {
|
||||||
|
CKComponentScope::replaceState(scope, newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
RCTSurfaceHostingComponent *const component =
|
||||||
|
[super newWithView:{[UIView class]} size:{}];
|
||||||
|
|
||||||
|
if (component) {
|
||||||
|
component->_state = scope.state();
|
||||||
|
component->_surface = surface;
|
||||||
|
component->_options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CKComponentLayout)computeLayoutThatFits:(CKSizeRange)constrainedSize
|
||||||
|
{
|
||||||
|
// Optimistically communicating layout constraints to the `_surface`,
|
||||||
|
// just to provide layout constraints to React Native as early as possible.
|
||||||
|
// React Native *may* use this info later during applying the own state and
|
||||||
|
// related laying out in parallel with ComponentKit execution.
|
||||||
|
// This call will not interfere (or introduce any negative side effects) with
|
||||||
|
// following invocation of `sizeThatFitsMinimumSize:maximumSize:`.
|
||||||
|
// A weak point: We assume here that this particular layout will be
|
||||||
|
// mounted eventually, which is technically not guaranteed by ComponentKit.
|
||||||
|
// Therefore we also assume that the last layout calculated in a sequence
|
||||||
|
// will be mounted anyways, which is probably true for all *real* use cases.
|
||||||
|
// We plan to tackle this problem during the next big step in improving
|
||||||
|
// interop compatibilities of React Native which will enable us granularly
|
||||||
|
// control React Native mounting blocks and, as a result, implement
|
||||||
|
// truly synchronous mounting stage between React Native and ComponentKit.
|
||||||
|
[_surface setMinimumSize:constrainedSize.min
|
||||||
|
maximumSize:constrainedSize.max];
|
||||||
|
|
||||||
|
// Just in case of the very first building pass, we give React Native a chance
|
||||||
|
// to prepare its internals for coming synchronous measuring.
|
||||||
|
[_surface synchronouslyWaitForStage:RCTSurfaceStageSurfaceDidInitialLayout
|
||||||
|
timeout:_options.synchronousLayoutingTimeout];
|
||||||
|
|
||||||
|
CGSize fittingSize = CGSizeZero;
|
||||||
|
if (_surface.stage & RCTSurfaceStageSurfaceDidInitialLayout) {
|
||||||
|
fittingSize = [_surface sizeThatFitsMinimumSize:constrainedSize.min
|
||||||
|
maximumSize:constrainedSize.max];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fittingSize = _options.activityIndicatorSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
fittingSize = constrainedSize.clamp(fittingSize);
|
||||||
|
return {self, fittingSize};
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CKComponentBoundsAnimation)boundsAnimationFromPreviousComponent:(RCTSurfaceHostingComponent *)previousComponent
|
||||||
|
{
|
||||||
|
if (_options.boundsAnimations && (previousComponent->_state.stage != _state.stage)) {
|
||||||
|
return {
|
||||||
|
.mode = CKComponentBoundsAnimationModeDefault,
|
||||||
|
.duration = 0.25,
|
||||||
|
.options = UIViewAnimationOptionCurveEaseInOut,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,14 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-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 <ComponentKit/CKComponentController.h>
|
||||||
|
|
||||||
|
@interface RCTSurfaceHostingComponentController : CKComponentController
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,143 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-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 "RCTSurfaceHostingComponentController.h"
|
||||||
|
|
||||||
|
#import <ComponentKit/CKComponentSubclass.h>
|
||||||
|
#import <React/RCTAssert.h>
|
||||||
|
#import <React/RCTSurface.h>
|
||||||
|
#import <React/RCTSurfaceDelegate.h>
|
||||||
|
#import <React/RCTSurfaceView.h>
|
||||||
|
|
||||||
|
#import "RCTSurfaceHostingComponent+Internal.h"
|
||||||
|
#import "RCTSurfaceHostingComponent.h"
|
||||||
|
#import "RCTSurfaceHostingComponentState.h"
|
||||||
|
|
||||||
|
@interface RCTSurfaceHostingComponentController() <RCTSurfaceDelegate>
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation RCTSurfaceHostingComponentController {
|
||||||
|
RCTSurface *_surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)initWithComponent:(RCTSurfaceHostingComponent *)component
|
||||||
|
{
|
||||||
|
if (self = [super initWithComponent:component]) {
|
||||||
|
[self updateSurfaceWithComponent:component];
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Lifecycle
|
||||||
|
|
||||||
|
- (void)didMount
|
||||||
|
{
|
||||||
|
[super didMount];
|
||||||
|
[self mountSurfaceView];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)didRemount
|
||||||
|
{
|
||||||
|
[super didRemount];
|
||||||
|
[self mountSurfaceView];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)didUpdateComponent
|
||||||
|
{
|
||||||
|
[super didUpdateComponent];
|
||||||
|
[self updateSurfaceWithComponent:(RCTSurfaceHostingComponent *)self.component];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)didUnmount
|
||||||
|
{
|
||||||
|
[super didUnmount];
|
||||||
|
[self unmountSurfaceView];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Helpers
|
||||||
|
|
||||||
|
- (void)updateSurfaceWithComponent:(RCTSurfaceHostingComponent *)component
|
||||||
|
{
|
||||||
|
// Updating `surface`
|
||||||
|
RCTSurface *const surface = component.surface;
|
||||||
|
if (surface != _surface) {
|
||||||
|
if (_surface.delegate == self) {
|
||||||
|
_surface.delegate = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
_surface = surface;
|
||||||
|
_surface.delegate = self;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setIntrinsicSize:(CGSize)intrinsicSize
|
||||||
|
{
|
||||||
|
[self.component updateState:^(RCTSurfaceHostingComponentState *state) {
|
||||||
|
return [RCTSurfaceHostingComponentState newWithStage:state.stage
|
||||||
|
intrinsicSize:intrinsicSize];
|
||||||
|
} mode:[self suitableStateUpdateMode]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setStage:(RCTSurfaceStage)stage
|
||||||
|
{
|
||||||
|
[self.component updateState:^(RCTSurfaceHostingComponentState *state) {
|
||||||
|
return [RCTSurfaceHostingComponentState newWithStage:stage
|
||||||
|
intrinsicSize:state.intrinsicSize];
|
||||||
|
} mode:[self suitableStateUpdateMode]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CKUpdateMode)suitableStateUpdateMode
|
||||||
|
{
|
||||||
|
return ((RCTSurfaceHostingComponent *)self.component).options.synchronousStateUpdates && RCTIsMainQueue() ? CKUpdateModeSynchronous : CKUpdateModeAsynchronous;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)mountSurfaceView
|
||||||
|
{
|
||||||
|
UIView *const surfaceView = _surface.view;
|
||||||
|
|
||||||
|
const CKComponentViewContext &context = [[self component] viewContext];
|
||||||
|
|
||||||
|
UIView *const superview = context.view;
|
||||||
|
superview.clipsToBounds = YES;
|
||||||
|
|
||||||
|
RCTAssert([superview.subviews count] <= 1, @"Should never have more than a single stateful subview.");
|
||||||
|
|
||||||
|
UIView *const existingSurfaceView = [superview.subviews lastObject];
|
||||||
|
if (existingSurfaceView != surfaceView) {
|
||||||
|
[existingSurfaceView removeFromSuperview];
|
||||||
|
surfaceView.frame = superview.bounds;
|
||||||
|
surfaceView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||||
|
[superview addSubview:surfaceView];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)unmountSurfaceView
|
||||||
|
{
|
||||||
|
const CKComponentViewContext &context = [[self component] viewContext];
|
||||||
|
|
||||||
|
UIView *const superview = context.view;
|
||||||
|
RCTAssert([superview.subviews count] <= 1, @"Should never have more than a single stateful subview.");
|
||||||
|
UIView *const existingSurfaceView = [superview.subviews lastObject];
|
||||||
|
[existingSurfaceView removeFromSuperview];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - RCTSurfaceDelegate
|
||||||
|
|
||||||
|
- (void)surface:(RCTSurface *)surface didChangeIntrinsicSize:(CGSize)intrinsicSize
|
||||||
|
{
|
||||||
|
[self setIntrinsicSize:intrinsicSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)surface:(RCTSurface *)surface didChangeStage:(RCTSurfaceStage)stage
|
||||||
|
{
|
||||||
|
[self setStage:stage];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,22 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-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 <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
#import <ComponentKit/CKComponent.h>
|
||||||
|
|
||||||
|
typedef CKComponent *(^RCTSurfaceHostingComponentOptionsActivityIndicatorComponentFactory)();
|
||||||
|
|
||||||
|
struct RCTSurfaceHostingComponentOptions {
|
||||||
|
NSTimeInterval synchronousLayoutingTimeout = 0.350;
|
||||||
|
BOOL synchronousStateUpdates = YES;
|
||||||
|
CGSize activityIndicatorSize = {44.0, 44.0};
|
||||||
|
BOOL boundsAnimations = YES;
|
||||||
|
RCTSurfaceHostingComponentOptionsActivityIndicatorComponentFactory activityIndicatorComponentFactory = nil;
|
||||||
|
};
|
|
@ -0,0 +1,22 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-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 <UIKit/UIKit.h>
|
||||||
|
|
||||||
|
#import <React/RCTSurfaceStage.h>
|
||||||
|
|
||||||
|
@interface RCTSurfaceHostingComponentState: NSObject
|
||||||
|
|
||||||
|
@property (nonatomic, readonly, assign) CGSize intrinsicSize;
|
||||||
|
@property (nonatomic, readonly, assign) RCTSurfaceStage stage;
|
||||||
|
|
||||||
|
+ (instancetype)newWithStage:(RCTSurfaceStage)stage
|
||||||
|
intrinsicSize:(CGSize)intrinsicSize;
|
||||||
|
|
||||||
|
@end
|
|
@ -0,0 +1,43 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-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 "RCTSurfaceHostingComponentState.h"
|
||||||
|
|
||||||
|
@implementation RCTSurfaceHostingComponentState
|
||||||
|
|
||||||
|
+ (instancetype)newWithStage:(RCTSurfaceStage)stage
|
||||||
|
intrinsicSize:(CGSize)intrinsicSize
|
||||||
|
{
|
||||||
|
return [[self alloc] initWithStage:stage intrinsicSize:intrinsicSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
- (instancetype)initWithStage:(RCTSurfaceStage)stage
|
||||||
|
intrinsicSize:(CGSize)intrinsicSize
|
||||||
|
{
|
||||||
|
if (self = [super init]) {
|
||||||
|
_stage = stage;
|
||||||
|
_intrinsicSize = intrinsicSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isEqual:(RCTSurfaceHostingComponentState *)other
|
||||||
|
{
|
||||||
|
if (other == self) {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
_stage == other->_stage &&
|
||||||
|
CGSizeEqualToSize(_intrinsicSize, other->_intrinsicSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
Loading…
Reference in New Issue