[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
@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)
*/
@property (nullable, nonatomic, copy) NSArray<NSNumber *> *pointSizeScaleFactors;

View File

@@ -45,6 +45,8 @@
+ (void)adjustFontSizeForAttributeString:(NSMutableAttributedString *)attrString withScaleFactor:(CGFloat)scaleFactor
{
if (scaleFactor == 1.0) return;
[attrString beginEditing];
// scale all the attributes that will change the bounding box
@@ -133,7 +135,10 @@
__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) {
// Check for two different situations (and correct for both)
@@ -149,53 +154,50 @@
}
}
NSUInteger scaleIndex = 0;
// find the longest word and make sure it fits in the constrained width
if ([longestWordNeedingResize length] > 0) {
// check to see if we may need to shrink for any of these things
BOOL longestWordFits = [longestWordNeedingResize length] ? NO : YES;
BOOL maxLinesFits = _attributes.maximumNumberOfLines > 0 ? NO : YES;
BOOL heightFits = isinf(_constrainedSize.height) ? YES : NO;
CGSize longestWordSize = CGSizeZero;
if (longestWordFits == NO) {
NSRange longestWordRange = [str rangeOfString:longestWordNeedingResize];
NSMutableAttributedString *attrString = [textStorage attributedSubstringFromRange:longestWordRange].mutableCopy;
CGSize longestWordSize = [attrString boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin context:nil].size;
NSAttributedString *attrString = [textStorage attributedSubstringFromRange:longestWordRange];
longestWordSize = [attrString boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin context:nil].size;
}
// check if the longest word is larger than our constrained width
if (longestWordSize.width > _constrainedSize.width) {
// we have a word that is too long. Loop through our scale factors until we fit
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
// we may need to shrink for some reason, so let's iterate through our scale factors to see if we actually need to shrink
// 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) {
if (longestWordFits && maxLinesFits && heightFits) {
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) {
// get the number of lines in our possibly scaled string
NSUInteger numberOfLines = [self lineCountForString:textStorage];
if (numberOfLines > _attributes.maximumNumberOfLines) {
// if the longest word fits, go ahead and check max line and height. If it didn't fit continue to the next scale factor
if (longestWordFits == YES) {
for (NSUInteger index = scaleIndex; index < scaleFactors.count; index++) {
NSMutableAttributedString *entireAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:textStorage];
[[self class] adjustFontSizeForAttributeString:entireAttributedString withScaleFactor:[scaleFactors[index] floatValue]];
// scale our string by the current scale factor
NSMutableAttributedString *scaledString = [[NSMutableAttributedString alloc] initWithAttributedString:textStorage];
[[self class] adjustFontSizeForAttributeString:scaledString withScaleFactor:adjustedScale];
// save away this scale factor. Even if we don't fit completely we should still scale down
adjustedScale = [scaleFactors[index] floatValue];
if ([self lineCountForString:entireAttributedString] <= _attributes.maximumNumberOfLines) {
// we fit! we are done
break;
}
// check to see if this scaled string fit in the max lines
if (maxLinesFits == NO) {
maxLinesFits = ([self lineCountForString:scaledString] <= _attributes.maximumNumberOfLines);
}
// 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
_imageNode = [[ASNetworkImageNode alloc] init];
_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];
CGFloat scale = [UIScreen mainScreen].scale;
@@ -91,8 +91,8 @@ static const CGFloat kInnerPadding = 10.0f;
_textNode = [[ASTextNode alloc] init];
_textNode.attributedText = [[NSAttributedString alloc] initWithString:[self kittyIpsum]
attributes:[self textStyle]];
_textNode.flexShrink = YES;
_textNode.flexGrow = YES;
_textNode.style.flexShrink = YES;
_textNode.style.flexGrow = YES;
[self addSubnode:_textNode];
return self;
@@ -141,10 +141,10 @@ static const CGFloat kInnerPadding = 10.0f;
[stackSpec setChildren:@[_imageNode, _textNode]];
if (self.asyncTraitCollection.horizontalSizeClass == UIUserInterfaceSizeClassRegular) {
_imageNode.alignSelf = ASStackLayoutAlignSelfStart;
_imageNode.style.alignSelf = ASStackLayoutAlignSelfStart;
stackSpec.direction = ASStackLayoutDirectionHorizontal;
} else {
_imageNode.alignSelf = ASStackLayoutAlignSelfCenter;
_imageNode.style.alignSelf = ASStackLayoutAlignSelfCenter;
stackSpec.direction = ASStackLayoutDirectionVertical;
}

View File

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