2015-03-23 20:28:42 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2015-02-20 04:10:52 +00:00
|
|
|
|
|
|
|
#import "RCTText.h"
|
|
|
|
|
|
|
|
#import "RCTShadowText.h"
|
|
|
|
#import "RCTUtils.h"
|
2015-03-26 09:58:06 +00:00
|
|
|
#import "UIView+React.h"
|
2015-02-20 04:10:52 +00:00
|
|
|
|
|
|
|
@implementation RCTText
|
|
|
|
{
|
|
|
|
NSLayoutManager *_layoutManager;
|
|
|
|
NSTextStorage *_textStorage;
|
|
|
|
NSTextContainer *_textContainer;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (instancetype)initWithFrame:(CGRect)frame
|
|
|
|
{
|
|
|
|
if ((self = [super initWithFrame:frame])) {
|
|
|
|
_textStorage = [[NSTextStorage alloc] init];
|
|
|
|
|
|
|
|
self.contentMode = UIViewContentModeRedraw;
|
|
|
|
}
|
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSAttributedString *)attributedText
|
|
|
|
{
|
|
|
|
return [_textStorage copy];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setAttributedText:(NSAttributedString *)attributedText
|
|
|
|
{
|
2015-04-09 14:40:18 +00:00
|
|
|
for (NSLayoutManager *existingLayoutManager in _textStorage.layoutManagers) {
|
|
|
|
[_textStorage removeLayoutManager:existingLayoutManager];
|
|
|
|
}
|
|
|
|
|
|
|
|
_textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedText];
|
|
|
|
|
|
|
|
if (_layoutManager) {
|
|
|
|
[_textStorage addLayoutManager:_layoutManager];
|
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
[self setNeedsDisplay];
|
|
|
|
}
|
|
|
|
|
2015-04-07 09:19:49 +00:00
|
|
|
- (void)setTextContainer:(NSTextContainer *)textContainer
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2015-04-09 14:40:18 +00:00
|
|
|
if ([_textContainer isEqual:textContainer]) {
|
|
|
|
return;
|
|
|
|
}
|
2015-04-07 09:19:49 +00:00
|
|
|
|
|
|
|
_textContainer = textContainer;
|
|
|
|
|
|
|
|
for (NSInteger i = _layoutManager.textContainers.count - 1; i >= 0; i--) {
|
|
|
|
[_layoutManager removeTextContainerAtIndex:i];
|
|
|
|
}
|
2015-04-09 14:40:18 +00:00
|
|
|
|
|
|
|
if (_textContainer) {
|
|
|
|
[_layoutManager addTextContainer:_textContainer];
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
|
|
|
|
[self setNeedsDisplay];
|
|
|
|
}
|
|
|
|
|
2015-04-07 09:19:49 +00:00
|
|
|
- (void)setLayoutManager:(NSLayoutManager *)layoutManager
|
2015-02-20 04:10:52 +00:00
|
|
|
{
|
2015-04-09 14:40:18 +00:00
|
|
|
if ([_layoutManager isEqual:layoutManager]) {
|
|
|
|
return;
|
|
|
|
}
|
2015-04-07 09:19:49 +00:00
|
|
|
|
|
|
|
_layoutManager = layoutManager;
|
|
|
|
|
2015-04-07 20:43:08 +00:00
|
|
|
for (NSLayoutManager *existingLayoutManager in _textStorage.layoutManagers) {
|
|
|
|
[_textStorage removeLayoutManager:existingLayoutManager];
|
2015-04-07 09:19:49 +00:00
|
|
|
}
|
2015-04-09 14:40:18 +00:00
|
|
|
|
|
|
|
if (_layoutManager) {
|
|
|
|
[_textStorage addLayoutManager:_layoutManager];
|
|
|
|
}
|
2015-02-20 04:10:52 +00:00
|
|
|
|
|
|
|
[self setNeedsDisplay];
|
|
|
|
}
|
|
|
|
|
2015-03-27 16:36:33 +00:00
|
|
|
- (CGRect)textFrame
|
|
|
|
{
|
|
|
|
return UIEdgeInsetsInsetRect(self.bounds, _contentInset);
|
|
|
|
}
|
|
|
|
|
2015-02-20 04:10:52 +00:00
|
|
|
- (void)layoutSubviews
|
|
|
|
{
|
|
|
|
[super layoutSubviews];
|
|
|
|
|
|
|
|
// The header comment for `size` says that a height of 0.0 should be enough,
|
|
|
|
// but it isn't.
|
2015-03-27 16:36:33 +00:00
|
|
|
_textContainer.size = CGSizeMake([self textFrame].size.width, CGFLOAT_MAX);
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)drawRect:(CGRect)rect
|
|
|
|
{
|
2015-03-27 16:36:33 +00:00
|
|
|
CGPoint origin = [self textFrame].origin;
|
2015-02-20 04:10:52 +00:00
|
|
|
NSRange glyphRange = [_layoutManager glyphRangeForTextContainer:_textContainer];
|
2015-03-27 16:36:33 +00:00
|
|
|
[_layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:origin];
|
|
|
|
[_layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:origin];
|
2015-02-20 04:10:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (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
|