diff --git a/AsyncDisplayKit/ASTextNode+Beta.h b/AsyncDisplayKit/ASTextNode+Beta.h index 130daa9360..e5d34678cc 100644 --- a/AsyncDisplayKit/ASTextNode+Beta.h +++ b/AsyncDisplayKit/ASTextNode+Beta.h @@ -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 *pointSizeScaleFactors; diff --git a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm index e1b5f601d7..81329d1e7e 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm +++ b/AsyncDisplayKit/TextKit/ASTextKitFontSizeAdjuster.mm @@ -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,56 +154,53 @@ } } - NSUInteger scaleIndex = 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; - // find the longest word and make sure it fits in the constrained width - if ([longestWordNeedingResize length] > 0) { + CGSize longestWordSize = CGSizeZero; + if (longestWordFits == NO) { + NSRange longestWordRange = [str rangeOfString:longestWordNeedingResize]; + NSAttributedString *attrString = [textStorage attributedSubstringFromRange:longestWordRange]; + longestWordSize = [attrString boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin context:nil].size; + } + + // 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; + } - 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; + adjustedScale = [adjustedScaleObj floatValue]; - // check if the longest word is larger than our constrained width - if (longestWordSize.width > _constrainedSize.width) { + if (longestWordFits == NO) { + // we need to check the longest word to make sure it fits + longestWordFits = std::ceil((longestWordSize.width * adjustedScale) <= _constrainedSize.width); + } + + // 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) { - // 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 - break; - } + // scale our string by the current scale factor + NSMutableAttributedString *scaledString = [[NSMutableAttributedString alloc] initWithAttributedString:textStorage]; + [[self class] adjustFontSizeForAttributeString:scaledString withScaleFactor:adjustedScale]; + + // 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); } } } - - if (_attributes.maximumNumberOfLines > 0) { - // get the number of lines in our possibly scaled string - NSUInteger numberOfLines = [self lineCountForString:textStorage]; - if (numberOfLines > _attributes.maximumNumberOfLines) { - - for (NSUInteger index = scaleIndex; index < scaleFactors.count; index++) { - NSMutableAttributedString *entireAttributedString = [[NSMutableAttributedString alloc] initWithAttributedString:textStorage]; - [[self class] adjustFontSizeForAttributeString:entireAttributedString withScaleFactor:[scaleFactors[index] floatValue]]; - - - // 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; - } - } - - } - } - + }]; _measured = YES; _scaleFactor = adjustedScale; diff --git a/examples_extra/ASTraitCollection/Sample/KittenNode.m b/examples_extra/ASTraitCollection/Sample/KittenNode.m index 0a9dacbde6..630db76b1a 100644 --- a/examples_extra/ASTraitCollection/Sample/KittenNode.m +++ b/examples_extra/ASTraitCollection/Sample/KittenNode.m @@ -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; } diff --git a/examples_extra/ASTraitCollection/Sample/OverrideViewController.m b/examples_extra/ASTraitCollection/Sample/OverrideViewController.m index 1b16fe9975..1148c907eb 100644 --- a/examples_extra/ASTraitCollection/Sample/OverrideViewController.m +++ b/examples_extra/ASTraitCollection/Sample/OverrideViewController.m @@ -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];