Skip to content

Commit

Permalink
Merge pull request facebook#6 from skillz/feature-text-resizing
Browse files Browse the repository at this point in the history
Feature - adjustsFontSizeToFit
  • Loading branch information
Tj committed Aug 13, 2015
2 parents 0070a09 + 51b3913 commit eb49594
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 6 deletions.
2 changes: 2 additions & 0 deletions Libraries/Text/RCTShadowText.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ extern NSString *const RCTReactTagAttributeName;
@property (nonatomic, assign) RCTTextDecorationLineType textDecorationLine;
@property (nonatomic, assign) CGFloat fontSizeMultiplier;
@property (nonatomic, assign) BOOL allowFontScaling;
@property (nonatomic, assign) BOOL adjustsFontSizeToFit;
@property (nonatomic, assign) CGFloat minimumFontScale;

- (void)recomputeText;

Expand Down
10 changes: 7 additions & 3 deletions Libraries/Text/RCTShadowText.m
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ - (NSDictionary *)processUpdatedProperties:(NSMutableSet *)applierBlocks
NSTextStorage *textStorage = [self buildTextStorageForWidth:self.frame.size.width];
[applierBlocks addObject:^(RCTSparseArray *viewRegistry) {
RCTText *view = viewRegistry[self.reactTag];
view.adjustsFontSizeToFit = self.adjustsFontSizeToFit;
view.minimumFontScale = self.minimumFontScale;
view.textStorage = textStorage;
}];

Expand Down Expand Up @@ -327,6 +329,7 @@ - (void)set##setProp:(type)value; \
[self dirtyText]; \
}

RCT_TEXT_PROPERTY(AdjustsFontSizeToFit, _adjustsFontSizeToFit, BOOL)
RCT_TEXT_PROPERTY(Color, _color, UIColor *)
RCT_TEXT_PROPERTY(FontFamily, _fontFamily, NSString *)
RCT_TEXT_PROPERTY(FontSize, _fontSize, CGFloat)
Expand All @@ -335,12 +338,13 @@ - (void)set##setProp:(type)value; \
RCT_TEXT_PROPERTY(IsHighlighted, _isHighlighted, BOOL)
RCT_TEXT_PROPERTY(LetterSpacing, _letterSpacing, CGFloat)
RCT_TEXT_PROPERTY(LineHeight, _lineHeight, CGFloat)
RCT_TEXT_PROPERTY(MinimumFontScale, _minimumFontScale, CGFloat)
RCT_TEXT_PROPERTY(NumberOfLines, _numberOfLines, NSUInteger)
RCT_TEXT_PROPERTY(ShadowOffset, _shadowOffset, CGSize)
RCT_TEXT_PROPERTY(TextAlign, _textAlign, NSTextAlignment)
RCT_TEXT_PROPERTY(TextDecorationColor, _textDecorationColor, UIColor *);
RCT_TEXT_PROPERTY(TextDecorationLine, _textDecorationLine, RCTTextDecorationLineType);
RCT_TEXT_PROPERTY(TextDecorationStyle, _textDecorationStyle, NSUnderlineStyle);
RCT_TEXT_PROPERTY(TextDecorationColor, _textDecorationColor, UIColor *)
RCT_TEXT_PROPERTY(TextDecorationLine, _textDecorationLine, RCTTextDecorationLineType)
RCT_TEXT_PROPERTY(TextDecorationStyle, _textDecorationStyle, NSUnderlineStyle)
RCT_TEXT_PROPERTY(WritingDirection, _writingDirection, NSWritingDirection)

- (void)setAllowFontScaling:(BOOL)allowFontScaling
Expand Down
3 changes: 3 additions & 0 deletions Libraries/Text/RCTText.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@
@property (nonatomic, assign) UIEdgeInsets contentInset;
@property (nonatomic, strong) NSTextStorage *textStorage;

@property BOOL adjustsFontSizeToFitWidth;
@property CGFloat minimumFontScale;

@end
115 changes: 113 additions & 2 deletions Libraries/Text/RCTText.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
#import "RCTUtils.h"
#import "UIView+React.h"

@interface RCTText ()

@property NSAttributedString *originalString;

@end

@implementation RCTText
{
NSTextStorage *_textStorage;
Expand All @@ -25,7 +31,9 @@ - (instancetype)initWithFrame:(CGRect)frame
if ((self = [super initWithFrame:frame])) {
_textStorage = [[NSTextStorage alloc] init];
_reactSubviews = [NSMutableArray array];

_minimumFontScale = .5;
_adjustsFontSizeToFitWidth = YES;

self.isAccessibilityElement = YES;
self.accessibilityTraits |= UIAccessibilityTraitStaticText;

Expand Down Expand Up @@ -70,16 +78,24 @@ - (NSArray *)reactSubviews
- (void)setTextStorage:(NSTextStorage *)textStorage
{
_textStorage = textStorage;
//This produces an NSMutableAttributedString and not an NSTextStorage
//(Perhaps resolve by doing this on purpose instead of via this mutableCopy side effect?)
_originalString = [textStorage mutableCopy];

[self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect
{
NSLayoutManager *layoutManager = [_textStorage.layoutManagers firstObject];
NSTextContainer *textContainer = [layoutManager.textContainers firstObject];
CGRect textFrame = UIEdgeInsetsInsetRect(self.bounds, _contentInset);
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];

CGRect textFrame = UIEdgeInsetsInsetRect(self.bounds, _contentInset);
if (_adjustsFontSizeToFitWidth) {
textFrame = [self updateToFitFrame:textFrame];
}

[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:textFrame.origin];
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:textFrame.origin];

Expand Down Expand Up @@ -149,6 +165,101 @@ - (void)didMoveToWindow
}
}

#pragma mark Sizing

- (CGRect)updateToFitFrame:(CGRect)frame
{
NSLayoutManager *layoutManager = [_textStorage.layoutManagers firstObject];
NSTextContainer *textContainer = [layoutManager.textContainers firstObject];
[textContainer setLineBreakMode:NSLineBreakByWordWrapping];

NSRange glyphRange = NSMakeRange(0, _textStorage.length);

[self resetDrawnTextStorage];

CGSize requiredSize = [self calculateSize:_textStorage];
NSInteger linesRequired = [self numberOfLinesRequired:layoutManager];

__block BOOL hitMinimumScale = NO;
while ((requiredSize.height > CGRectGetHeight(frame) ||
requiredSize.width > CGRectGetWidth(frame) ||
(linesRequired > textContainer.maximumNumberOfLines &&
textContainer.maximumNumberOfLines != 0))
&& !hitMinimumScale)
{
[_textStorage beginEditing];
[_textStorage enumerateAttribute:NSFontAttributeName
inRange:glyphRange
options:0
usingBlock:^(UIFont *font, NSRange range, BOOL *stop)
{
if (font) {
UIFont *originalFont = [_originalString attribute:NSFontAttributeName
atIndex:range.location
effectiveRange:&range];
UIFont *newFont = [font fontWithSize:font.pointSize - .5];
if (newFont.pointSize > originalFont.pointSize * self.minimumFontScale) {
[_textStorage removeAttribute:NSFontAttributeName range:range];
[_textStorage addAttribute:NSFontAttributeName value:newFont range:range];
} else {
hitMinimumScale = YES;
}
}
}];
[_textStorage endEditing];

linesRequired = [self numberOfLinesRequired:layoutManager];
requiredSize = [self calculateSize:_textStorage];
}

//Vertically center draw position
frame.origin.y = _contentInset.top + round((CGRectGetHeight(frame) - requiredSize.height) / 2);
return frame;
}

// Via Apple Text Layout Programming Guide
// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/TextLayout/Tasks/CountLines.html
- (NSInteger)numberOfLinesRequired:(NSLayoutManager *)layoutManager
{
NSInteger numberOfLines, index, numberOfGlyphs = [layoutManager numberOfGlyphs];
NSRange lineRange;
for (numberOfLines = 0, index = 0; index < numberOfGlyphs; numberOfLines++){
(void) [layoutManager lineFragmentRectForGlyphAtIndex:index
effectiveRange:&lineRange];
index = NSMaxRange(lineRange);
}

return numberOfLines;
}

// Via Apple Text Layout Programming Guide
//https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/TextLayout/Tasks/StringHeight.html
- (CGSize)calculateSize:(NSTextStorage *)storage
{
NSLayoutManager *layoutManager = [storage.layoutManagers firstObject];
NSTextContainer *textContainer = [layoutManager.textContainers firstObject];
(void) [layoutManager glyphRangeForTextContainer:textContainer];
return [layoutManager usedRectForTextContainer:textContainer].size;
}

//Start fresh with the original drawn string each time, in case frame has gotten larger
- (void)resetDrawnTextStorage
{
[_textStorage beginEditing];

NSRange originalRange = NSMakeRange(0, _originalString.length);
[_textStorage setAttributes:@{} range:originalRange];

[_originalString enumerateAttributesInRange:originalRange
options:0
usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop)
{
[_textStorage setAttributes:attrs range:range];
}];

[_textStorage endEditing];
}

#pragma mark - Accessibility

- (NSString *)accessibilityLabel
Expand Down
2 changes: 2 additions & 0 deletions Libraries/Text/RCTTextManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ - (RCTShadowView *)shadowView
RCT_EXPORT_SHADOW_PROPERTY(textDecorationLine, RCTTextDecorationLineType)
RCT_EXPORT_SHADOW_PROPERTY(writingDirection, NSWritingDirection)
RCT_EXPORT_SHADOW_PROPERTY(allowFontScaling, BOOL)
RCT_EXPORT_SHADOW_PROPERTY(adjustsFontSizeToFit, BOOL)
RCT_EXPORT_SHADOW_PROPERTY(minimumFontScale, CGFloat)

- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry
{
Expand Down
13 changes: 12 additions & 1 deletion Libraries/Text/Text.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ var viewConfig = {
isHighlighted: true,
numberOfLines: true,
allowFontScaling: true,
adjustsFontSizeToFit: true,
minimumFontScale: true,
}),
uiViewClassName: 'RCTText',
};
Expand Down Expand Up @@ -104,6 +106,15 @@ var Text = React.createClass({
* Specifies should fonts scale to respect Text Size accessibility setting on iOS.
*/
allowFontScaling: React.PropTypes.bool,
/**
* Specifies that font should shrink to fit container size.
*/
adjustsFontSizeToFit: React.PropTypes.bool,
/**
* Minimum amount that a font should scale down to fit container.
* Used in conjuction with adjustsFontSizeToFit
*/
minimumFontScale: React.PropTypes.number,
},

viewConfig: viewConfig,
Expand All @@ -113,7 +124,7 @@ var Text = React.createClass({
isHighlighted: false,
});
},

getDefaultProps: function(): Object {
return {
allowFontScaling: true,
Expand Down

0 comments on commit eb49594

Please sign in to comment.