Created
May 15, 2015 21:27
-
-
Save chirag04/70a2b4082b836b68c543 to your computer and use it in GitHub Desktop.
Core changes for tooltip with text idea
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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