Skip to content

Instantly share code, notes, and snippets.

@chirag04
Created May 15, 2015 21:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chirag04/70a2b4082b836b68c543 to your computer and use it in GitHub Desktop.
Save chirag04/70a2b4082b836b68c543 to your computer and use it in GitHub Desktop.
Core changes for tooltip with text idea
/**
* 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>
@class RCTEventDispatcher;
@interface RCTText : UIView
@property (nonatomic, strong) NSLayoutManager *layoutManager;
@property (nonatomic, strong) NSTextContainer *textContainer;
@property (nonatomic, copy) NSAttributedString *attributedText;
@property (nonatomic, assign) UIEdgeInsets contentInset;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@end
/**
* 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 "RCTText.h"
#import "RCTEventDispatcher.h"
#import "RCTShadowText.h"
#import "RCTUtils.h"
#import "UIView+React.h"
@implementation RCTText
{
RCTEventDispatcher *_eventDispatcher;
NSLayoutManager *_layoutManager;
NSTextStorage *_textStorage;
NSTextContainer *_textContainer;
}
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super initWithFrame:CGRectZero])) {
_eventDispatcher = eventDispatcher;
_textStorage = [[NSTextStorage alloc] init];
self.opaque = NO;
self.contentMode = UIViewContentModeRedraw;
}
return self;
}
- (NSAttributedString *)attributedText
{
return [_textStorage copy];
}
- (void)setAttributedText:(NSAttributedString *)attributedText
{
for (NSLayoutManager *existingLayoutManager in _textStorage.layoutManagers) {
[_textStorage removeLayoutManager:existingLayoutManager];
}
_textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedText];
if (_layoutManager) {
[_textStorage addLayoutManager:_layoutManager];
}
[self setNeedsDisplay];
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
reactTag:self.reactTag
text:[attributedText string]];
}
- (void)setTextContainer:(NSTextContainer *)textContainer
{
if ([_textContainer isEqual:textContainer]) {
return;
}
_textContainer = textContainer;
for (NSInteger i = _layoutManager.textContainers.count - 1; i >= 0; i--) {
[_layoutManager removeTextContainerAtIndex:i];
}
if (_textContainer) {
[_layoutManager addTextContainer:_textContainer];
}
[self setNeedsDisplay];
}
- (void)setLayoutManager:(NSLayoutManager *)layoutManager
{
if ([_layoutManager isEqual:layoutManager]) {
return;
}
_layoutManager = layoutManager;
for (NSLayoutManager *existingLayoutManager in _textStorage.layoutManagers) {
[_textStorage removeLayoutManager:existingLayoutManager];
}
if (_layoutManager) {
[_textStorage addLayoutManager:_layoutManager];
}
[self setNeedsDisplay];
}
- (CGRect)textFrame
{
return UIEdgeInsetsInsetRect(self.bounds, _contentInset);
}
- (void)drawRect:(CGRect)rect
{
CGRect textFrame = [self textFrame];
// We reset the text container size every time because RCTShadowText's
// RCTMeasure overrides it. The header comment for `size` says that a height
// of 0.0 should be enough, but it isn't.
_textContainer.size = CGSizeMake(textFrame.size.width, CGFLOAT_MAX);
NSRange glyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer];
[_layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:textFrame.origin];
[_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:textFrame.origin];
}
- (NSNumber *)reactTagAtPoint:(CGPoint)point
{
CGFloat fraction;
NSUInteger characterIndex = [_layoutManager characterIndexForPoint:point
inTextContainer:_textContainer
fractionOfDistanceBetweenInsertionPoints:&fraction];
NSNumber *reactTag = nil;
// If the point is not before (fraction == 0.0) the first character and not
// after (fraction == 1.0) the last character, then the attribute is valid.
if (_textStorage.length > 0 && (fraction > 0 || characterIndex > 0) && (fraction < 1 || characterIndex < _textStorage.length - 1)) {
reactTag = [_textStorage attribute:RCTReactTagAttributeName atIndex:characterIndex effectiveRange:NULL];
}
return reactTag ?: self.reactTag;
}
@end
/**
* 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 "RCTTextManager.h"
#import "RCTBridge.h"
#import "RCTAssert.h"
#import "RCTConvert.h"
#import "RCTLog.h"
#import "RCTShadowRawText.h"
#import "RCTShadowText.h"
#import "RCTSparseArray.h"
#import "RCTText.h"
#import "UIView+React.h"
@implementation RCTTextManager
RCT_EXPORT_MODULE()
- (UIView *)view
{
return [[RCTText alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
}
- (RCTShadowView *)shadowView
{
return [[RCTShadowText alloc] init];
}
#pragma mark - View properties
RCT_REMAP_VIEW_PROPERTY(containerBackgroundColor, backgroundColor, UIColor)
#pragma mark - Shadow properties
RCT_EXPORT_SHADOW_PROPERTY(writingDirection, NSWritingDirection)
RCT_EXPORT_SHADOW_PROPERTY(color, UIColor)
RCT_EXPORT_SHADOW_PROPERTY(fontFamily, NSString)
RCT_EXPORT_SHADOW_PROPERTY(fontSize, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(fontWeight, NSString)
RCT_EXPORT_SHADOW_PROPERTY(fontStyle, NSString)
RCT_EXPORT_SHADOW_PROPERTY(isHighlighted, BOOL)
RCT_EXPORT_SHADOW_PROPERTY(lineHeight, CGFloat)
RCT_EXPORT_SHADOW_PROPERTY(maximumNumberOfLines, NSInteger)
RCT_EXPORT_SHADOW_PROPERTY(shadowOffset, CGSize)
RCT_EXPORT_SHADOW_PROPERTY(textAlign, NSTextAlignment)
RCT_REMAP_SHADOW_PROPERTY(backgroundColor, textBackgroundColor, UIColor)
RCT_CUSTOM_SHADOW_PROPERTY(containerBackgroundColor, UIColor, RCTShadowText)
{
view.backgroundColor = json ? [RCTConvert UIColor:json] : defaultView.backgroundColor;
view.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet;
}
RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, NSInteger, RCTShadowText)
{
NSLineBreakMode truncationMode = NSLineBreakByClipping;
view.maximumNumberOfLines = json ? [RCTConvert NSInteger:json] : defaultView.maximumNumberOfLines;
if (view.maximumNumberOfLines > 0) {
truncationMode = NSLineBreakByTruncatingTail;
}
view.truncationMode = truncationMode;
}
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry
{
NSMutableArray *uiBlocks = [NSMutableArray new];
for (RCTShadowView *rootView in shadowViewRegistry.allObjects) {
if (![rootView isReactRootView]) {
// This isn't a root view
continue;
}
if (![rootView isTextDirty]) {
// No text processing to be done
continue;
}
RCTSparseArray *reactTaggedAttributedStrings = [[RCTSparseArray alloc] init];
NSMutableArray *queue = [NSMutableArray arrayWithObject:rootView];
for (NSInteger i = 0; i < [queue count]; i++) {
RCTShadowView *shadowView = queue[i];
RCTAssert([shadowView isTextDirty], @"Don't process any nodes that don't have dirty text");
if ([shadowView isKindOfClass:[RCTShadowText class]]) {
RCTShadowText *shadowText = (RCTShadowText *)shadowView;
reactTaggedAttributedStrings[shadowText.reactTag] = [shadowText attributedString];
} else if ([shadowView isKindOfClass:[RCTShadowRawText class]]) {
RCTLogError(@"Raw text cannot be used outside of a <Text> tag. Not rendering string: '%@'", [(RCTShadowRawText *)shadowView text]);
} else {
for (RCTShadowView *child in [shadowView reactSubviews]) {
if ([child isTextDirty]) {
[queue addObject:child];
}
}
}
[shadowView setTextComputed];
}
[uiBlocks addObject:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
[reactTaggedAttributedStrings enumerateObjectsUsingBlock:^(NSAttributedString *attributedString, NSNumber *reactTag, BOOL *stop) {
RCTText *text = viewRegistry[reactTag];
text.attributedText = attributedString;
}];
}];
}
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
for (RCTViewManagerUIBlock shadowBlock in uiBlocks) {
shadowBlock(uiManager, viewRegistry);
}
};
}
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowText *)shadowView
{
NSNumber *reactTag = shadowView.reactTag;
UIEdgeInsets padding = shadowView.paddingAsInsets;
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
RCTText *text = viewRegistry[reactTag];
text.contentInset = padding;
text.layoutManager = shadowView.layoutManager;
text.textContainer = shadowView.textContainer;
};
}
@end
/**
* 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 Text
* @flow
*/
'use strict';
var NativeMethodsMixin = require('NativeMethodsMixin');
var React = require('React');
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
var StyleSheetPropType = require('StyleSheetPropType');
var TextStylePropTypes = require('TextStylePropTypes');
var Touchable = require('Touchable');
var createReactIOSNativeComponentClass =
require('createReactIOSNativeComponentClass');
var merge = require('merge');
var stylePropType = StyleSheetPropType(TextStylePropTypes);
var viewConfig = {
validAttributes: merge(ReactIOSViewAttributes.UIView, {
isHighlighted: true,
numberOfLines: true,
}),
uiViewClassName: 'RCTText',
};
/**
* A React component for displaying text which supports nesting,
* styling, and touch handling. In the following example, the nested title and
* body text will inherit the `fontFamily` from `styles.baseText`, but the title
* provides its own additional styles. The title and body will stack on top of
* each other on account of the literal newlines:
*
* ```
* renderText: function() {
* return (
* <Text style={styles.baseText}>
* <Text style={styles.titleText} onPress={this.onPressTitle}>
* {this.state.titleText + '\n\n'}
* </Text>
* <Text numberOfLines={5}>
* {this.state.bodyText}
* </Text>
* </Text>
* );
* },
* ...
* var styles = StyleSheet.create({
* baseText: {
* fontFamily: 'Cochin',
* },
* titleText: {
* fontSize: 20,
* fontWeight: 'bold',
* },
* };
* ```
*/
var Text = React.createClass({
mixins: [Touchable.Mixin, NativeMethodsMixin],
propTypes: {
/**
* Used to truncate the text with an elipsis after computing the text
* layout, including line wrapping, such that the total number of lines does
* not exceed this number.
*/
numberOfLines: React.PropTypes.number,
/**
* This function is called on press. Text intrinsically supports press
* handling with a default highlight state (which can be disabled with
* `suppressHighlighting`).
*/
onPress: React.PropTypes.func,
/**
* Callback that is called when the text input's text changes.
*/
onChange: React.PropTypes.func,
/**
* When true, no visual change is made when text is pressed down. By
* default, a gray oval highlights the text on press down.
*/
suppressHighlighting: React.PropTypes.bool,
style: stylePropType,
/**
* Used to locate this view in end-to-end tests.
*/
testID: React.PropTypes.string,
},
viewConfig: viewConfig,
getInitialState: function() {
return merge(this.touchableGetInitialState(), {
isHighlighted: false,
});
},
onStartShouldSetResponder: function(): bool {
var shouldSetFromProps = this.props.onStartShouldSetResponder &&
this.props.onStartShouldSetResponder();
return shouldSetFromProps || !!this.props.onPress;
},
/*
* Returns true to allow responder termination
*/
handleResponderTerminationRequest: function(): bool {
// Allow touchable or props.onResponderTerminationRequest to deny
// the request
var allowTermination = this.touchableHandleResponderTerminationRequest();
if (allowTermination && this.props.onResponderTerminationRequest) {
allowTermination = this.props.onResponderTerminationRequest();
}
return allowTermination;
},
handleResponderGrant: function(e: SyntheticEvent, dispatchID: string) {
this.touchableHandleResponderGrant(e, dispatchID);
this.props.onResponderGrant &&
this.props.onResponderGrant.apply(this, arguments);
},
handleResponderMove: function(e: SyntheticEvent) {
this.touchableHandleResponderMove(e);
this.props.onResponderMove &&
this.props.onResponderMove.apply(this, arguments);
},
handleResponderRelease: function(e: SyntheticEvent) {
this.touchableHandleResponderRelease(e);
this.props.onResponderRelease &&
this.props.onResponderRelease.apply(this, arguments);
},
handleResponderTerminate: function(e: SyntheticEvent) {
this.touchableHandleResponderTerminate(e);
this.props.onResponderTerminate &&
this.props.onResponderTerminate.apply(this, arguments);
},
touchableHandleActivePressIn: function() {
if (this.props.suppressHighlighting || !this.props.onPress) {
return;
}
this.setState({
isHighlighted: true,
});
},
touchableHandleActivePressOut: function() {
if (this.props.suppressHighlighting || !this.props.onPress) {
return;
}
this.setState({
isHighlighted: false,
});
},
touchableHandlePress: function() {
this.props.onPress && this.props.onPress();
},
touchableGetPressRectOffset: function(): RectOffset {
return PRESS_RECT_OFFSET;
},
_onChange: function(event: Event) {
this.props.onChange && this.props.onChange(event);
},
render: function() {
var props = {};
for (var key in this.props) {
props[key] = this.props[key];
}
props.ref = this.getNodeHandle();
// Text is accessible by default
if (props.accessible !== false) {
props.accessible = true;
}
props.onChange = this._onChange;
props.isHighlighted = this.state.isHighlighted;
props.onStartShouldSetResponder = this.onStartShouldSetResponder;
props.onResponderTerminationRequest =
this.handleResponderTerminationRequest;
props.onResponderGrant = this.handleResponderGrant;
props.onResponderMove = this.handleResponderMove;
props.onResponderRelease = this.handleResponderRelease;
props.onResponderTerminate = this.handleResponderTerminate;
return <RCTText {...props} />;
},
});
type RectOffset = {
top: number;
left: number;
right: number;
bottom: number;
}
var PRESS_RECT_OFFSET = {top: 20, left: 20, right: 20, bottom: 30};
var RCTText = createReactIOSNativeComponentClass(viewConfig);
module.exports = Text;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment