Skip to content

Commit

Permalink
RCTSurface: RCTSurfaceHostingView, interop layer beteween UIKit and R…
Browse files Browse the repository at this point in the history
…CTSurface

Summary: UIView subclass which providers interoperability between UIKit and Surface regarding layout and life-cycle.

Reviewed By: rsnara

Differential Revision: D6465922

fbshipit-source-id: 2a7cfb70119d460bc22968d1aa780833bf47d7f6
  • Loading branch information
shergin authored and facebook-github-bot committed Dec 4, 2017
1 parent c8e72bb commit 4d37cf0
Show file tree
Hide file tree
Showing 5 changed files with 380 additions and 1 deletion.
1 change: 1 addition & 0 deletions React/Base/Surface/RCTSurface.mm
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ - (void)_registerRootView
}

RCTUIManager *uiManager = batchedBridge.uiManager;

RCTExecuteOnUIManagerQueue(^{
[uiManager registerRootViewTag:self->_rootViewTag];

Expand Down
69 changes: 69 additions & 0 deletions React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* 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/RCTSurfaceSizeMeasureMode.h>
#import <React/RCTSurfaceStage.h>

@class RCTBridge;
@class RCTSurface;

typedef UIView *(^RCTSurfaceHostingViewActivityIndicatorViewFactory)();

NS_ASSUME_NONNULL_BEGIN

/**
* UIView subclass which providers interoperability between UIKit and
* Surface regarding layout and life-cycle.
* This class can be used as easy-to-use general purpose integration point
* of ReactNative-powered experiences in UIKit based apps.
*/
@interface RCTSurfaceHostingView : UIView

/**
* Designated initializer.
* Instanciates a view with given Surface object.
* Note: The view retains the surface object.
*/
- (instancetype)initWithSurface:(RCTSurface *)surface NS_DESIGNATED_INITIALIZER;

/**
* Convenience initializer.
* Instanciates a Surface object with given `bridge`, `moduleName`, and
* `initialProperties`, and then use it to instanciate a view.
*/
- (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties;

/**
* Surface object which is currently using to power the view.
* Read-only.
*/
@property (nonatomic, strong, readonly) RCTSurface *surface;

/**
* Size measure mode which are defining relationship between UIKit and ReactNative
* layout approaches.
* Defaults to `RCTSurfaceSizeMeasureModeWidthAtMost | RCTSurfaceSizeMeasureModeHeightAtMost`.
*/
@property (nonatomic, assign) RCTSurfaceSizeMeasureMode sizeMeasureMode;

/**
* Activity indicator factory.
* A hosting view may use this block to instantiate and display custom activity
* (loading) indicator (aka "spinner") when it needed.
* Defaults to `nil` (no activity indicator).
*/
@property (nonatomic, copy, nullable) RCTSurfaceHostingViewActivityIndicatorViewFactory activityIndicatorViewFactory;

@end

NS_ASSUME_NONNULL_END
224 changes: 224 additions & 0 deletions React/Base/Surface/SurfaceHostingView/RCTSurfaceHostingView.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
/**
* 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 "RCTSurfaceHostingView.h"

#import "RCTDefines.h"
#import "RCTSurface.h"
#import "RCTSurfaceDelegate.h"
#import "RCTSurfaceView.h"
#import "RCTUtils.h"

@interface RCTSurfaceHostingView () <RCTSurfaceDelegate>

@property (nonatomic, assign) BOOL isActivityIndicatorViewVisible;
@property (nonatomic, assign) BOOL isSurfaceViewVisible;

@end

@implementation RCTSurfaceHostingView {
UIView *_Nullable _activityIndicatorView;
UIView *_Nullable _surfaceView;
RCTSurfaceStage _stage;
}

RCT_NOT_IMPLEMENTED(- (instancetype)init)
RCT_NOT_IMPLEMENTED(- (instancetype)initWithFrame:(CGRect)frame)
RCT_NOT_IMPLEMENTED(- (nullable instancetype)initWithCoder:(NSCoder *)coder)

- (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties
{
RCTSurface *surface =
[[RCTSurface alloc] initWithBridge:bridge
moduleName:moduleName
initialProperties:initialProperties];

return [self initWithSurface:surface];
}

- (instancetype)initWithSurface:(RCTSurface *)surface
{
if (self = [super initWithFrame:CGRectZero]) {
_surface = surface;

_sizeMeasureMode =
RCTSurfaceSizeMeasureModeWidthAtMost |
RCTSurfaceSizeMeasureModeHeightAtMost;

_surface.delegate = self;
_stage = surface.stage;
[self _updateViews];
}

return self;
}

- (CGSize)intrinsicContentSize
{
if (RCTSurfaceStageIsPreparing(_stage)) {
if (_activityIndicatorView) {
return _activityIndicatorView.intrinsicContentSize;
}

return CGSizeZero;
}

return _surface.intrinsicSize;
}

- (CGSize)sizeThatFits:(CGSize)size
{
if (RCTSurfaceStageIsPreparing(_stage)) {
if (_activityIndicatorView) {
return [_activityIndicatorView sizeThatFits:size];
}

return CGSizeZero;
}

CGSize minumumSize = CGSizeZero;
CGSize maximumSize = CGSizeMake(INFINITY, INFINITY);

if (_sizeMeasureMode & RCTSurfaceSizeMeasureModeWidthExact) {
minumumSize.width = size.width;
maximumSize.width = size.width;
}
else if (_sizeMeasureMode & RCTSurfaceSizeMeasureModeWidthAtMost) {
maximumSize.width = size.width;
}

if (_sizeMeasureMode & RCTSurfaceSizeMeasureModeHeightExact) {
minumumSize.height = size.height;
maximumSize.height = size.height;
}
else if (_sizeMeasureMode & RCTSurfaceSizeMeasureModeHeightAtMost) {
maximumSize.height = size.height;
}

return [_surface sizeThatFitsMinimumSize:minumumSize
maximumSize:maximumSize];
}

- (void)setStage:(RCTSurfaceStage)stage
{
if (stage == _stage) {
return;
}

BOOL shouldInvalidateLayout =
RCTSurfaceStageIsRunning(stage) != RCTSurfaceStageIsRunning(_stage) ||
RCTSurfaceStageIsPreparing(stage) != RCTSurfaceStageIsPreparing(_stage);

_stage = stage;

if (shouldInvalidateLayout) {
[self _invalidateLayout];
[self _updateViews];
}
}

- (void)setSizeMeasureMode:(RCTSurfaceSizeMeasureMode)sizeMeasureMode
{
if (sizeMeasureMode == _sizeMeasureMode) {
return;
}

_sizeMeasureMode = sizeMeasureMode;
[self _invalidateLayout];
}

#pragma mark - isActivityIndicatorViewVisible

- (void)setIsActivityIndicatorViewVisible:(BOOL)visible
{
if (_isActivityIndicatorViewVisible == visible) {
return;
}

if (visible) {
if (_activityIndicatorViewFactory) {
_activityIndicatorView = _activityIndicatorViewFactory();
_activityIndicatorView.frame = self.bounds;
_activityIndicatorView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self addSubview:_activityIndicatorView];
}
} else {
[_activityIndicatorView removeFromSuperview];
_activityIndicatorView = nil;
}
}

#pragma mark - isSurfaceViewVisible

- (void)setIsSurfaceViewVisible:(BOOL)visible
{
if (_isSurfaceViewVisible == visible) {
return;
}

if (visible) {
_surfaceView = _surface.view;
_surfaceView.frame = self.bounds;
_surfaceView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self addSubview:_surfaceView];
} else {
[_surfaceView removeFromSuperview];
_surfaceView = nil;
}
}

#pragma mark - activityIndicatorViewFactory

- (void)setActivityIndicatorViewFactory:(RCTSurfaceHostingViewActivityIndicatorViewFactory)activityIndicatorViewFactory
{
_activityIndicatorViewFactory = activityIndicatorViewFactory;
if (_isActivityIndicatorViewVisible) {
_isActivityIndicatorViewVisible = NO;
self.isActivityIndicatorViewVisible = YES;
}
}

#pragma mark - Private stuff

- (void)_invalidateLayout
{
[self.superview setNeedsLayout];
}

- (void)_updateViews
{
self.isSurfaceViewVisible = RCTSurfaceStageIsRunning(_stage);
self.isActivityIndicatorViewVisible = RCTSurfaceStageIsPreparing(_stage);
}

- (void)didMoveToWindow
{
[super didMoveToWindow];
[self _updateViews];
}

#pragma mark - RCTSurfaceDelegate

- (void)surface:(RCTSurface *)surface didChangeStage:(RCTSurfaceStage)stage
{
RCTExecuteOnMainQueue(^{
[self setStage:stage];
});
}

- (void)surface:(RCTSurface *)surface didChangeIntrinsicSize:(CGSize)intrinsicSize
{
RCTExecuteOnMainQueue(^{
[self _invalidateLayout];
});
}

@end
23 changes: 23 additions & 0 deletions React/Base/Surface/SurfaceHostingView/RCTSurfaceSizeMeasureMode.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* 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>

/**
* Bitmask defines how size constrains from `-[UIView sizeThatFits:]`
* are translated to `-[RCTSurface sizeThatFitsMinimumSize:maximumSize:]`.
*/
typedef NS_OPTIONS(NSInteger, RCTSurfaceSizeMeasureMode) {
RCTSurfaceSizeMeasureModeWidthUndefined = 0 << 0,
RCTSurfaceSizeMeasureModeWidthExact = 1 << 0,
RCTSurfaceSizeMeasureModeWidthAtMost = 2 << 0,
RCTSurfaceSizeMeasureModeHeightUndefined = 0 << 2,
RCTSurfaceSizeMeasureModeHeightExact = 1 << 2,
RCTSurfaceSizeMeasureModeHeightAtMost = 2 << 2,
};
Loading

1 comment on commit 4d37cf0

@shergin
Copy link
Contributor Author

@shergin shergin commented on 4d37cf0 Dec 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @talkol
Hey Tal!
You can try to use it for implementing truly sync initial rendering/layout!

  • Comment this part:
    if (RCTIsMainQueue()) {
    RCTLogInfo(@"Synchronous waiting is not supported on the main queue.");
    return NO;
    }
  • Instantiate a RCTSurface object;
  • Create a RCTSurfaceHostingView using the surface;
  • Call synchronouslyWaitForStage:timeout: where you need sync waiting;
  • Profit.

I would love to hear any feedback. 😄

Please sign in to comment.