From e75bd87a76726a9b075061ef76156705b3c1e872 Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Sat, 11 Nov 2017 21:15:17 -0800 Subject: [PATCH] Introducing RCTSurfaceHostingComponent Summary: RCTSurfaceHostingComponent is ComponentKit component represents given Surface instance. Differential Revision: D6217104 fbshipit-source-id: 50805d97e744de24a188bc97b33de4709e785aae --- .../RCTSurfaceHostingComponent+Internal.h | 22 +++ .../RCTSurfaceHostingComponent.h | 22 +++ .../RCTSurfaceHostingComponent.mm | 104 +++++++++++++ .../RCTSurfaceHostingComponentController.h | 14 ++ .../RCTSurfaceHostingComponentController.mm | 143 ++++++++++++++++++ .../RCTSurfaceHostingComponentOptions.h | 22 +++ .../RCTSurfaceHostingComponentState.h | 22 +++ .../RCTSurfaceHostingComponentState.mm | 43 ++++++ 8 files changed, 392 insertions(+) create mode 100644 Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponent+Internal.h create mode 100644 Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponent.h create mode 100644 Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponent.mm create mode 100644 Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponentController.h create mode 100644 Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponentController.mm create mode 100644 Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponentOptions.h create mode 100644 Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponentState.h create mode 100644 Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponentState.mm diff --git a/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponent+Internal.h b/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponent+Internal.h new file mode 100644 index 000000000..184ff32a9 --- /dev/null +++ b/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponent+Internal.h @@ -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 +#import + +@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 diff --git a/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponent.h b/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponent.h new file mode 100644 index 000000000..c1dd823af --- /dev/null +++ b/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponent.h @@ -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 +#import + +@class RCTSurface; + +/** + * ComponentKit component represents given Surface instance. + */ +@interface RCTSurfaceHostingComponent : CKComponent + ++ (instancetype)newWithSurface:(RCTSurface *)surface options:(RCTSurfaceHostingComponentOptions)options; + +@end diff --git a/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponent.mm b/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponent.mm new file mode 100644 index 000000000..010bd961c --- /dev/null +++ b/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponent.mm @@ -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 + +#import +#import +#import + +#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 diff --git a/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponentController.h b/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponentController.h new file mode 100644 index 000000000..04b8fcafa --- /dev/null +++ b/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponentController.h @@ -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 + +@interface RCTSurfaceHostingComponentController : CKComponentController + +@end diff --git a/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponentController.mm b/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponentController.mm new file mode 100644 index 000000000..c44dbcd00 --- /dev/null +++ b/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponentController.mm @@ -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 +#import +#import +#import +#import + +#import "RCTSurfaceHostingComponent+Internal.h" +#import "RCTSurfaceHostingComponent.h" +#import "RCTSurfaceHostingComponentState.h" + +@interface RCTSurfaceHostingComponentController() +@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 diff --git a/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponentOptions.h b/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponentOptions.h new file mode 100644 index 000000000..001cbd84e --- /dev/null +++ b/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponentOptions.h @@ -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 + +#import + +typedef CKComponent *(^RCTSurfaceHostingComponentOptionsActivityIndicatorComponentFactory)(); + +struct RCTSurfaceHostingComponentOptions { + NSTimeInterval synchronousLayoutingTimeout = 0.350; + BOOL synchronousStateUpdates = YES; + CGSize activityIndicatorSize = {44.0, 44.0}; + BOOL boundsAnimations = YES; + RCTSurfaceHostingComponentOptionsActivityIndicatorComponentFactory activityIndicatorComponentFactory = nil; +}; diff --git a/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponentState.h b/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponentState.h new file mode 100644 index 000000000..b1279aaa5 --- /dev/null +++ b/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponentState.h @@ -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 + +#import + +@interface RCTSurfaceHostingComponentState: NSObject + +@property (nonatomic, readonly, assign) CGSize intrinsicSize; +@property (nonatomic, readonly, assign) RCTSurfaceStage stage; + ++ (instancetype)newWithStage:(RCTSurfaceStage)stage + intrinsicSize:(CGSize)intrinsicSize; + +@end diff --git a/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponentState.mm b/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponentState.mm new file mode 100644 index 000000000..f5573db67 --- /dev/null +++ b/Libraries/SurfaceHostingComponent/RCTSurfaceHostingComponentState.mm @@ -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