Skip to content

Commit

Permalink
Introducing RCTSurfaceHostingComponent
Browse files Browse the repository at this point in the history
Summary: RCTSurfaceHostingComponent is ComponentKit component represents given Surface instance.

Differential Revision: D6217104

fbshipit-source-id: 50805d97e744de24a188bc97b33de4709e785aae
  • Loading branch information
shergin authored and facebook-github-bot committed Nov 12, 2017
1 parent 6d92046 commit e75bd87
Show file tree
Hide file tree
Showing 8 changed files with 392 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponent.h
Original file line number Diff line number Diff line change
@@ -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
104 changes: 104 additions & 0 deletions Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponent.mm
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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;
};
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit e75bd87

Please sign in to comment.