From 77c8237ffe00b2cd3786548d3ef8e7f84043ef1d Mon Sep 17 00:00:00 2001 From: Adam Miskiewicz Date: Fri, 7 Jul 2017 12:15:58 -0700 Subject: [PATCH] MaskedViewIOS -- A way to apply alpha masks to views on iOS --- .../MaskedView/MaskedViewIOS.android.js | 13 ++ .../MaskedView/MaskedViewIOS.ios.js | 167 ++++++++++++++++++ .../react-native-implementation.js | 1 + RNTester/js/MaskedViewExample.js | 95 ++++++++++ RNTester/js/RNTesterList.ios.js | 5 + RNTester/js/imageMask.png | Bin 0 -> 22290 bytes React/React.xcodeproj/project.pbxproj | 24 +++ React/Views/RCTMaskedView.h | 17 ++ React/Views/RCTMaskedView.m | 45 +++++ React/Views/RCTMaskedViewManager.h | 14 ++ React/Views/RCTMaskedViewManager.m | 35 ++++ website/server/docsList.js | 1 + 12 files changed, 417 insertions(+) create mode 100644 Libraries/Components/MaskedView/MaskedViewIOS.android.js create mode 100644 Libraries/Components/MaskedView/MaskedViewIOS.ios.js create mode 100644 RNTester/js/MaskedViewExample.js create mode 100644 RNTester/js/imageMask.png create mode 100644 React/Views/RCTMaskedView.h create mode 100644 React/Views/RCTMaskedView.m create mode 100644 React/Views/RCTMaskedViewManager.h create mode 100644 React/Views/RCTMaskedViewManager.m diff --git a/Libraries/Components/MaskedView/MaskedViewIOS.android.js b/Libraries/Components/MaskedView/MaskedViewIOS.android.js new file mode 100644 index 000000000000..68f4a11f65d3 --- /dev/null +++ b/Libraries/Components/MaskedView/MaskedViewIOS.android.js @@ -0,0 +1,13 @@ +/** + * 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. + * + * @providesModule MaskedViewIOS + */ +'use strict'; + +module.exports = require('UnimplementedView'); diff --git a/Libraries/Components/MaskedView/MaskedViewIOS.ios.js b/Libraries/Components/MaskedView/MaskedViewIOS.ios.js new file mode 100644 index 000000000000..3581491da804 --- /dev/null +++ b/Libraries/Components/MaskedView/MaskedViewIOS.ios.js @@ -0,0 +1,167 @@ +/** + * 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. + * + * @providesModule MaskedViewIOS + * @flow + */ + +const React = require('React'); +const StyleSheet = require('StyleSheet'); +const View = require('View'); +const ViewPropTypes = require('ViewPropTypes'); +const findNodeHandle = require('ReactNative').findNodeHandle; +const requireNativeComponent = require('requireNativeComponent'); +const cloneReferencedElement = require('react-clone-referenced-element'); +const ReactPropTypes = React.PropTypes; + +import type { StyleObj } from 'StyleSheetTypes'; + +/** + * Renders the child view with a mask specified in the `renderMask` prop. + * + * ``` + * import React from 'react'; + * import { MaskedView, Text, View } from 'react-native'; + * + * class MyMaskedView extends React.Component { + * render() { + * return ( + * + * + * + * Basic Mask + * + * + * } + * > + * + * + * ); + * } + * } + * ``` + * + * The above example will render a view with a blue background that fills its + * parent, and then mask that view with text that says "Basic Mask". + * + * The alpha channel of the view rendered by the `renderMask` prop determines how + * much of the view’s content and background shows through. Fully or partially + * opaque pixels allow the underlying content to show through but fully + * transparent pixels block that content. + * + */ +class MaskedViewIOS extends React.Component { + props: { + style?: StyleObj, + /** + * `MaskedView` only accepts a single child as a view to be masked. + */ + children: ReactElement<*>, + /** + * Should return a React element to be rendered and applied as the + * mask for the child element. + */ + renderMask: () => React.Element<*>, + }; + + state: { + maskViewNodeRef: ?mixed, + } = { + maskViewNodeRef: null, + }; + + static propTypes = { + ...ViewPropTypes, + renderMask: ReactPropTypes.func.isRequired, + }; + + _hasWarnedInvalidChildren = false; + _hasWarnedInvalidRenderMask = false; + _maskViewNodeHandle: ?number = null; + + componentWillUpdate(nextProps, nextState) { + if (nextState.maskViewNodeRef !== this.state.maskViewNodeRef) { + this._maskViewNodeHandle = findNodeHandle(nextState.maskViewNodeRef); + } + } + + render() { + if (!this.props.children || React.Children.count(this.props.children) > 1) { + if (!this._hasWarnedInvalidChildren) { + console.warn( + 'MaskedView: MaskedView requires a single child React Element.' + ); + this._hasWarnedInvalidChildren = true; + } + return null; + } + + const target = React.Children.only(this.props.children); + + if (typeof this.props.renderMask !== 'function') { + if (!this._hasWarnedInvalidRenderMask) { + console.warn( + 'MaskedView: Invalid `renderMask` prop was passed to MaskedView. ' + + 'Expected a function. No mask will render.' + ); + this._hasWarnedInvalidRenderMask = true; + } + return {target}; + } + + const maskElement = this.props.renderMask(); + let maskElementWithRef = null; + if (maskElement) { + maskElementWithRef = cloneReferencedElement(maskElement, { + ref: this._handleMaskViewRef, + }); + } + + return ( + + + {maskElementWithRef} + + {target} + + ); + } + + _handleMaskViewRef = (node: React.Element<*>) => { + if (!node) { + return; + } + if (this.state.maskViewNodeRef !== node) { + this.setState({ maskViewNodeRef: node }); + } + }; +} + +const RCTMaskedView = requireNativeComponent( + 'RCTMaskedView', + { + name: 'RCTMaskedView', + displayName: 'RCTMaskedView', + propTypes: { + maskRef: ReactPropTypes.number, + }, + }, + { + nativeOnly: { + maskRef: true, + }, + } +); + +module.exports = MaskedViewIOS; diff --git a/Libraries/react-native/react-native-implementation.js b/Libraries/react-native/react-native-implementation.js index f5c6b0a965a8..5dc92dbcc0ac 100644 --- a/Libraries/react-native/react-native-implementation.js +++ b/Libraries/react-native/react-native-implementation.js @@ -28,6 +28,7 @@ const ReactNative = { get ImageStore() { return require('ImageStore'); }, get KeyboardAvoidingView() { return require('KeyboardAvoidingView'); }, get ListView() { return require('ListView'); }, + get MaskedViewIOS() { return require('MaskedViewIOS'); }, get Modal() { return require('Modal'); }, get NavigatorIOS() { return require('NavigatorIOS'); }, get Picker() { return require('Picker'); }, diff --git a/RNTester/js/MaskedViewExample.js b/RNTester/js/MaskedViewExample.js new file mode 100644 index 000000000000..8ba59e752904 --- /dev/null +++ b/RNTester/js/MaskedViewExample.js @@ -0,0 +1,95 @@ +/** + * 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. + * + * @flow + * @providesModule MaskedViewExample + */ +'use strict'; + +const React = require('react'); +const RNTesterBlock = require('RNTesterBlock'); +const RNTesterPage = require('RNTesterPage'); +const { + Image, + MaskedViewIOS, + StyleSheet, + Text, + View, +} = require('react-native'); + +class MaskedViewExample extends React.Component { + static title = ''; + static description = 'Renders the child view with a mask specified in the `renderMask` prop.'; + + render() { + return ( + + + + + + + Basic Mask + + }> + + + + + + + + + + }> + + + + + + + + ); + } +} + +const styles = StyleSheet.create({ + maskContainerStyle: { + flex: 1, + backgroundColor: 'transparent', + justifyContent: 'center', + alignItems: 'center', + }, + maskTextStyle: { + backgroundColor: 'transparent', + fontSize: 40, + fontWeight: 'bold', + }, +}); + +module.exports = MaskedViewExample; diff --git a/RNTester/js/RNTesterList.ios.js b/RNTester/js/RNTesterList.ios.js index 0d92f58944c5..4853a1fd1411 100644 --- a/RNTester/js/RNTesterList.ios.js +++ b/RNTester/js/RNTesterList.ios.js @@ -68,6 +68,11 @@ const ComponentExamples: Array = [ module: require('./ListViewPagingExample'), supportsTVOS: true, }, + { + key: 'MaskedViewExample', + module: require('./MaskedViewExample'), + supportsTVOS: true, + }, { key: 'ModalExample', module: require('./ModalExample'), diff --git a/RNTester/js/imageMask.png b/RNTester/js/imageMask.png new file mode 100644 index 0000000000000000000000000000000000000000..07da178cc885edcca7153d04a87bce26d6eebe41 GIT binary patch literal 22290 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�v!xv#T26}wBZTYLUv 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 +#import "RCTView.h" + +@interface RCTMaskedView : RCTView + +- (void)setReactMaskView:(UIView*)view; + +@end diff --git a/React/Views/RCTMaskedView.m b/React/Views/RCTMaskedView.m new file mode 100644 index 000000000000..03f5201b60c3 --- /dev/null +++ b/React/Views/RCTMaskedView.m @@ -0,0 +1,45 @@ +/** + * 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 "RCTMaskedView.h" + +@implementation RCTMaskedView { + UIView *_reactMaskView; +} + +- (void) layoutSubviews +{ + [super layoutSubviews]; + + // When a mask is set on this view, we need to make sure + // the mask view isn't in the view hierarchy (iOS won't use + // the view as a mask if it exists in the view hierarchy). + // Then reset the `maskView` property to make sure that + // the mask is set correctly. + if (_reactMaskView != nil) { + if (_reactMaskView.superview != nil) { + [_reactMaskView removeFromSuperview]; + } + self.maskView = _reactMaskView; + } +} + +- (void)displayLayer:(CALayer *)layer +{ + // RCTView uses displayLayer to do border rendering. + // We don't need to do that in RCTMaskedView, so we + // stub this method and override the default implementation. +} + +- (void)setReactMaskView:(UIView*)view +{ + _reactMaskView = view; +} + +@end diff --git a/React/Views/RCTMaskedViewManager.h b/React/Views/RCTMaskedViewManager.h new file mode 100644 index 000000000000..9cf66573b5fd --- /dev/null +++ b/React/Views/RCTMaskedViewManager.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 RCTMaskedViewManager : RCTViewManager + +@end diff --git a/React/Views/RCTMaskedViewManager.m b/React/Views/RCTMaskedViewManager.m new file mode 100644 index 000000000000..f7c9895466ee --- /dev/null +++ b/React/Views/RCTMaskedViewManager.m @@ -0,0 +1,35 @@ +/** + * 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 "RCTMaskedViewManager.h" +#import "RCTMaskedView.h" +#import "RCTUIManager.h" + +@implementation RCTMaskedViewManager + +RCT_EXPORT_MODULE() + +- (UIView *)view +{ + return [RCTMaskedView new]; +} + +RCT_CUSTOM_VIEW_PROPERTY(maskRef, NSNumber, RCTMaskedView) +{ + NSNumber *maskRefTag = [RCTConvert NSNumber:json]; + UIView *maskView = [self.bridge.uiManager viewForReactTag:maskRefTag]; + if (maskView != nil) { + [view setReactMaskView:maskView]; + } else { + [view setReactMaskView:nil]; + view.maskView = defaultView.maskView; + } +} + +@end diff --git a/website/server/docsList.js b/website/server/docsList.js index ef4fb9ac4a4c..44401c461173 100644 --- a/website/server/docsList.js +++ b/website/server/docsList.js @@ -18,6 +18,7 @@ const components = [ '../Libraries/Image/Image.ios.js', '../Libraries/Components/Keyboard/KeyboardAvoidingView.js', '../Libraries/Lists/ListView/ListView.js', + '../Libraries/Components/MaskedView/MaskedViewIOS.ios.js', '../Libraries/Modal/Modal.js', '../Libraries/Components/Navigation/NavigatorIOS.ios.js', '../Libraries/Components/Picker/Picker.js',