/** * 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 "RCTShadowText.h" #import "RCTUtils.h" #import "UIView+React.h" @implementation RCTText { NSLayoutManager *_layoutManager; NSTextStorage *_textStorage; NSTextContainer *_textContainer; } - (instancetype)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { _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]; } - (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