[ASTextKitFontSizeAdjuster] Use the constrainedSize’s height to adjust font scaling (#2309)

* refactor shrinking logic

# Conflicts:
#	AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm

* fix ASTraitCollection sample

* updated comments.

* fix build errors

* adlai’s comments
This commit is contained in:
ricky
2016-10-04 10:01:50 -07:00
committed by Adlai Holler
parent d6e5e27c39
commit 872aad220f
4 changed files with 54 additions and 51 deletions

View File

@@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN
/** /**
@abstract An array of descending scale factors that will be applied to this text node to try to make it fit within its constrained size @abstract An array of descending scale factors that will be applied to this text node to try to make it fit within its constrained size
@discussion This array should be in descending order and NOT contain the scale factor 1.0. For example, it could return @[@(.9), @(.85), @(.8)];
@default nil (no scaling) @default nil (no scaling)
*/ */
@property (nullable, nonatomic, copy) NSArray<NSNumber *> *pointSizeScaleFactors; @property (nullable, nonatomic, copy) NSArray<NSNumber *> *pointSizeScaleFactors;

View File

@@ -45,6 +45,8 @@
+ (void)adjustFontSizeForAttributeString:(NSMutableAttributedString *)attrString withScaleFactor:(CGFloat)scaleFactor + (void)adjustFontSizeForAttributeString:(NSMutableAttributedString *)attrString withScaleFactor:(CGFloat)scaleFactor
{ {
if (scaleFactor == 1.0) return;
[attrString beginEditing]; [attrString beginEditing];
// scale all the attributes that will change the bounding box // scale all the attributes that will change the bounding box
@@ -133,7 +135,10 @@
__block CGFloat adjustedScale = 1.0; __block CGFloat adjustedScale = 1.0;
NSArray *scaleFactors = _attributes.pointSizeScaleFactors; // We add the scale factor of 1 to our scaleFactors array so that in the first iteration of the loop below, we are
// actually determining if we need to scale at all. If something doesn't fit, we will continue to iterate our scale factors.
NSArray *scaleFactors = [@[@(1)] arrayByAddingObjectsFromArray:_attributes.pointSizeScaleFactors];
[_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) { [_context performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
// Check for two different situations (and correct for both) // Check for two different situations (and correct for both)
@@ -149,53 +154,50 @@
} }
} }
NSUInteger scaleIndex = 0; // check to see if we may need to shrink for any of these things
BOOL longestWordFits = [longestWordNeedingResize length] ? NO : YES;
// find the longest word and make sure it fits in the constrained width BOOL maxLinesFits = _attributes.maximumNumberOfLines > 0 ? NO : YES;
if ([longestWordNeedingResize length] > 0) { BOOL heightFits = isinf(_constrainedSize.height) ? YES : NO;
CGSize longestWordSize = CGSizeZero;
if (longestWordFits == NO) {
NSRange longestWordRange = [str rangeOfString:longestWordNeedingResize]; NSRange longestWordRange = [str rangeOfString:longestWordNeedingResize];
NSMutableAttributedString *attrString = [textStorage attributedSubstringFromRange:longestWordRange].mutableCopy; NSAttributedString *attrString = [textStorage attributedSubstringFromRange:longestWordRange];
CGSize longestWordSize = [attrString boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin context:nil].size; longestWordSize = [attrString boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin context:nil].size;
}
// check if the longest word is larger than our constrained width // we may need to shrink for some reason, so let's iterate through our scale factors to see if we actually need to shrink
if (longestWordSize.width > _constrainedSize.width) { // Note: the first scale factor in the array is 1.0 so will make sure that things don't fit without shrinking
for (NSNumber *adjustedScaleObj in scaleFactors) {
// we have a word that is too long. Loop through our scale factors until we fit if (longestWordFits && maxLinesFits && heightFits) {
for (NSNumber *scaleFactor in scaleFactors) {
// even if we still don't fit, save this scaleFactor so more of the word will fit
adjustedScale = [scaleFactor floatValue];
// adjust here so we start at the proper place in our scale array if we have too many lines
scaleIndex++;
if (std::ceil(longestWordSize.width * [scaleFactor floatValue]) <= _constrainedSize.width) {
// we fit! we are done
break; break;
} }
}
} adjustedScale = [adjustedScaleObj floatValue];
if (longestWordFits == NO) {
// we need to check the longest word to make sure it fits
longestWordFits = std::ceil((longestWordSize.width * adjustedScale) <= _constrainedSize.width);
} }
if (_attributes.maximumNumberOfLines > 0) { // if the longest word fits, go ahead and check max line and height. If it didn't fit continue to the next scale factor
// get the number of lines in our possibly scaled string if (longestWordFits == YES) {
NSUInteger numberOfLines = [self lineCountForString:textStorage];
if (numberOfLines > _attributes.maximumNumberOfLines) {
for (NSUInteger index = scaleIndex; index < scaleFactors.count; index++) { // scale our string by the current scale factor
NSMutableAttributedString *entireAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:textStorage]; NSMutableAttributedString *scaledString = [[NSMutableAttributedString alloc] initWithAttributedString:textStorage];
[[self class] adjustFontSizeForAttributeString:entireAttributedString withScaleFactor:[scaleFactors[index] floatValue]]; [[self class] adjustFontSizeForAttributeString:scaledString withScaleFactor:adjustedScale];
// check to see if this scaled string fit in the max lines
// save away this scale factor. Even if we don't fit completely we should still scale down if (maxLinesFits == NO) {
adjustedScale = [scaleFactors[index] floatValue]; maxLinesFits = ([self lineCountForString:scaledString] <= _attributes.maximumNumberOfLines);
if ([self lineCountForString:entireAttributedString] <= _attributes.maximumNumberOfLines) {
// we fit! we are done
break;
}
} }
// if max lines still doesn't fit, continue without checking that we fit in the constrained height
if (maxLinesFits == YES && heightFits == NO) {
// max lines fit so make sure that we fit in the constrained height.
CGSize stringSize = [scaledString boundingRectWithSize:CGSizeMake(_constrainedSize.width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin context:nil].size;
heightFits = (stringSize.height <= _constrainedSize.height);
}
} }
} }

View File

@@ -77,7 +77,7 @@ static const CGFloat kInnerPadding = 10.0f;
// kitten image, with a solid background colour serving as placeholder // kitten image, with a solid background colour serving as placeholder
_imageNode = [[ASNetworkImageNode alloc] init]; _imageNode = [[ASNetworkImageNode alloc] init];
_imageNode.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); _imageNode.backgroundColor = ASDisplayNodeDefaultPlaceholderColor();
_imageNode.size = ASRelativeSizeRangeMakeWithExactCGSize(_kittenSize); _imageNode.style.size = (ASLayoutableSize){ .width = ASDimensionMake(_kittenSize.width), .height = ASDimensionMake(_kittenSize.height) };
[_imageNode addTarget:self action:@selector(imageTapped:) forControlEvents:ASControlNodeEventTouchUpInside]; [_imageNode addTarget:self action:@selector(imageTapped:) forControlEvents:ASControlNodeEventTouchUpInside];
CGFloat scale = [UIScreen mainScreen].scale; CGFloat scale = [UIScreen mainScreen].scale;
@@ -91,8 +91,8 @@ static const CGFloat kInnerPadding = 10.0f;
_textNode = [[ASTextNode alloc] init]; _textNode = [[ASTextNode alloc] init];
_textNode.attributedText = [[NSAttributedString alloc] initWithString:[self kittyIpsum] _textNode.attributedText = [[NSAttributedString alloc] initWithString:[self kittyIpsum]
attributes:[self textStyle]]; attributes:[self textStyle]];
_textNode.flexShrink = YES; _textNode.style.flexShrink = YES;
_textNode.flexGrow = YES; _textNode.style.flexGrow = YES;
[self addSubnode:_textNode]; [self addSubnode:_textNode];
return self; return self;
@@ -141,10 +141,10 @@ static const CGFloat kInnerPadding = 10.0f;
[stackSpec setChildren:@[_imageNode, _textNode]]; [stackSpec setChildren:@[_imageNode, _textNode]];
if (self.asyncTraitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular) { if (self.asyncTraitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular) {
_imageNode.alignSelf = ASStackLayoutAlignSelfStart; _imageNode.style.alignSelf = ASStackLayoutAlignSelfStart;
stackSpec.direction = ASStackLayoutDirectionHorizontal; stackSpec.direction = ASStackLayoutDirectionHorizontal;
} else { } else {
_imageNode.alignSelf = ASStackLayoutAlignSelfCenter; _imageNode.style.alignSelf = ASStackLayoutAlignSelfCenter;
stackSpec.direction = ASStackLayoutDirectionVertical; stackSpec.direction = ASStackLayoutDirectionVertical;
} }

View File

@@ -33,8 +33,8 @@ static NSString *kLinkAttributeName = @"PlaceKittenNodeLinkAttributeName";
return nil; return nil;
_textNode = [[ASTextNode alloc] init]; _textNode = [[ASTextNode alloc] init];
_textNode.flexGrow = YES; _textNode.style.flexGrow = YES;
_textNode.flexShrink = YES; _textNode.style.flexShrink = YES;
_textNode.maximumNumberOfLines = 3; _textNode.maximumNumberOfLines = 3;
[self addSubnode:_textNode]; [self addSubnode:_textNode];