diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index 217fe2677a..e5899f51c4 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/AsyncDisplayKit/ASEditableTextNode.mm @@ -49,8 +49,8 @@ // TextKit. ASDN::RecursiveMutex _textKitLock; - ASTextKitComponents _textKitComponents; - ASTextKitComponents _placeholderTextKitComponents; + ASTextKitComponents *_textKitComponents; + ASTextKitComponents *_placeholderTextKitComponents; // Forwards NSLayoutManagerDelegate methods related to word kerning ASTextNodeWordKerner *_wordKerner; @@ -74,12 +74,12 @@ _displayingPlaceholder = YES; // Create the scaffolding for the text view. - _textKitComponents = ASTextKitComponentsCreate(nil, CGSizeZero); + _textKitComponents = [ASTextKitComponents componentsWithAttributedSeedString:nil textContainerSize:CGSizeZero]; _textKitComponents.layoutManager.delegate = self; _wordKerner = [[ASTextNodeWordKerner alloc] init]; // Create the placeholder scaffolding. - _placeholderTextKitComponents = ASTextKitComponentsCreate(nil, CGSizeZero); + _placeholderTextKitComponents = [ASTextKitComponents componentsWithAttributedSeedString:nil textContainerSize:CGSizeZero]; _placeholderTextKitComponents.layoutManager.delegate = self; return self; @@ -133,8 +133,8 @@ - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { - ASTextKitComponents displayedComponents = [self isDisplayingPlaceholder] ? _placeholderTextKitComponents : _textKitComponents; - CGSize textSize = ASTextKitComponentsSizeForConstrainedWidth(displayedComponents, constrainedSize.width); + ASTextKitComponents *displayedComponents = [self isDisplayingPlaceholder] ? _placeholderTextKitComponents : _textKitComponents; + CGSize textSize = [displayedComponents sizeForConstrainedWidth:constrainedSize.width]; return CGSizeMake(constrainedSize.width, fminf(textSize.height, constrainedSize.height)); } diff --git a/AsyncDisplayKit/Details/ASTextNodeRenderer.mm b/AsyncDisplayKit/Details/ASTextNodeRenderer.mm index 06acd27722..d422114cf7 100644 --- a/AsyncDisplayKit/Details/ASTextNodeRenderer.mm +++ b/AsyncDisplayKit/Details/ASTextNodeRenderer.mm @@ -505,7 +505,8 @@ static const CGFloat ASTextNodeRendererTextCapHeightPadding = 1.3; CGRect lastLineRect = [_layoutManager lineFragmentRectForGlyphAtIndex:lastVisibleGlyphIndex effectiveRange:NULL]; // Calculate the bounding rectangle for the truncation message - ASTextKitComponents truncationComponents = ASTextKitComponentsCreate(_truncationString, constrainedRect.size); + ASTextKitComponents *truncationComponents = [ASTextKitComponents componentsWithAttributedSeedString:_truncationString + textContainerSize:constrainedRect.size]; // Size the truncation message [truncationComponents.layoutManager ensureLayoutForTextContainer:truncationComponents.textContainer]; diff --git a/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.h b/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.h index 0820315e1b..771c8ef775 100644 --- a/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.h +++ b/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.h @@ -11,26 +11,29 @@ #import #import -typedef struct { - NSTextStorage *textStorage; - NSTextContainer *textContainer; - NSLayoutManager *layoutManager; - UITextView *textView; -} ASTextKitComponents; +@interface ASTextKitComponents : NSObject -// Convenience. /** - @abstract Creates the stack of TextKit components. - @param attributedSeedString The attributed string to sed the returned text storage with, or nil to receive an blank text storage. - @param textContainerSize The size of the text-container. Typically, size specifies the constraining width of the layout, and FLT_MAX for height. Pass CGSizeZero if these components will be hooked up to a UITextView, which will manage the text container's size itself. - @return A `ASTextKitComponents` containing the created components. The text view component will be nil. - @discussion The returned components will be hooked up together, so they are ready for use as a system upon return. + @abstract Creates the stack of TextKit components. + @param attributedSeedString The attributed string to seed the returned text storage with, or nil to receive an blank text storage. + @param textContainerSize The size of the text-container. Typically, size specifies the constraining width of the layout, and FLT_MAX for height. Pass CGSizeZero if these components will be hooked up to a UITextView, which will manage the text container's size itself. + @return An `ASTextKitComponents` containing the created components. The text view component will be nil. + @discussion The returned components will be hooked up together, so they are ready for use as a system upon return. */ -extern ASTextKitComponents ASTextKitComponentsCreate(NSAttributedString *attributedSeedString, CGSize textContainerSize); ++ (ASTextKitComponents *)componentsWithAttributedSeedString:(NSAttributedString *)attributedSeedString + textContainerSize:(CGSize)textContainerSize; + /** - @abstract Returns the bounding size for the text view's text. - @param components The TextKit components to calculate the constrained size of the text for. - @param constrainedWidth The constraining width to be used during text-sizing. Usually, this value should be the receiver's calculated size. - @result A CGSize representing the bounding size for the receiver's text. + @abstract Returns the bounding size for the text view's text. + @param components The TextKit components to calculate the constrained size of the text for. + @param constrainedWidth The constraining width to be used during text-sizing. Usually, this value should be the receiver's calculated size. + @result A CGSize representing the bounding size for the receiver's text. */ -extern CGSize ASTextKitComponentsSizeForConstrainedWidth(ASTextKitComponents components, CGFloat constrainedWidth); +- (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth; + +@property (nonatomic, strong, readonly) NSTextStorage *textStorage; +@property (nonatomic, strong, readonly) NSTextContainer *textContainer; +@property (nonatomic, strong, readonly) NSLayoutManager *layoutManager; +@property (nonatomic, strong) UITextView *textView; + +@end diff --git a/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.mm b/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.mm index 2ca5109a46..3a6b603c3b 100644 --- a/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.mm +++ b/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.mm @@ -8,26 +8,21 @@ #import "ASTextNodeTextKitHelpers.h" -#pragma mark - Convenience +@interface ASTextKitComponents () -CGSize ASTextKitComponentsSizeForConstrainedWidth(ASTextKitComponents components, CGFloat constrainedWidth) +// read-write redeclarations +@property (nonatomic, strong, readwrite) NSTextStorage *textStorage; +@property (nonatomic, strong, readwrite) NSTextContainer *textContainer; +@property (nonatomic, strong, readwrite) NSLayoutManager *layoutManager; + +@end + +@implementation ASTextKitComponents + ++ (ASTextKitComponents *)componentsWithAttributedSeedString:(NSAttributedString *)attributedSeedString + textContainerSize:(CGSize)textContainerSize { - // If our text-view's width is already the constrained width, we can use our existing TextKit stack for this sizing calculation. - // Otherwise, we create a temporary stack to size for `constrainedWidth`. - if (CGRectGetWidth(components.textView.bounds) != constrainedWidth) { - components = ASTextKitComponentsCreate(components.textStorage, CGSizeMake(constrainedWidth, FLT_MAX)); - } - - // Force glyph generation and layout, which may not have happened yet (and isn't triggered by -usedRectForTextContainer:). - [components.layoutManager ensureLayoutForTextContainer:components.textContainer]; - CGSize textSize = [components.layoutManager usedRectForTextContainer:components.textContainer].size; - - return textSize; -} - -ASTextKitComponents ASTextKitComponentsCreate(NSAttributedString *attributedSeedString, CGSize textContainerSize) -{ - ASTextKitComponents components; + ASTextKitComponents *components = [[ASTextKitComponents alloc] init]; // Create the TextKit component stack with our default configuration. components.textStorage = (attributedSeedString ? [[NSTextStorage alloc] initWithAttributedString:attributedSeedString] : [[NSTextStorage alloc] init]); @@ -41,3 +36,22 @@ ASTextKitComponents ASTextKitComponentsCreate(NSAttributedString *attributedSeed return components; } + +- (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth +{ + ASTextKitComponents *components = self; + + // If our text-view's width is already the constrained width, we can use our existing TextKit stack for this sizing calculation. + // Otherwise, we create a temporary stack to size for `constrainedWidth`. + if (CGRectGetWidth(components.textView.bounds) != constrainedWidth) { + components = [ASTextKitComponents componentsWithAttributedSeedString:components.textStorage textContainerSize:CGSizeMake(constrainedWidth, FLT_MAX)]; + } + + // Force glyph generation and layout, which may not have happened yet (and isn't triggered by -usedRectForTextContainer:). + [components.layoutManager ensureLayoutForTextContainer:components.textContainer]; + CGSize textSize = [components.layoutManager usedRectForTextContainer:components.textContainer].size; + + return textSize; +} + +@end diff --git a/AsyncDisplayKitTests/ASTextNodeWordKernerTests.mm b/AsyncDisplayKitTests/ASTextNodeWordKernerTests.mm index ad53664620..994191ed5a 100644 --- a/AsyncDisplayKitTests/ASTextNodeWordKernerTests.mm +++ b/AsyncDisplayKitTests/ASTextNodeWordKernerTests.mm @@ -17,7 +17,7 @@ @interface ASTextNodeWordKernerTests : XCTestCase @property (nonatomic, readwrite, strong) ASTextNodeWordKerner *layoutManagerDelegate; -@property (nonatomic, readwrite, assign) ASTextKitComponents components; +@property (nonatomic, readwrite, assign) ASTextKitComponents *components; @property (nonatomic, readwrite, copy) NSAttributedString *attributedString; @end @@ -37,7 +37,7 @@ NSDictionary *attributes = nil; NSString *seedString = @"Hello world"; NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:seedString attributes:attributes]; - _components = ASTextKitComponentsCreate(attributedString, size); + _components = [ASTextKitComponents componentsWithAttributedSeedString:attributedString textContainerSize:size]; } - (void)setupTextKitComponentsWithWordKerning @@ -46,7 +46,7 @@ NSDictionary *attributes = @{ASTextNodeWordKerningAttributeName: @".5"}; NSString *seedString = @"Hello world"; NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:seedString attributes:attributes]; - _components = ASTextKitComponentsCreate(attributedString, size); + _components = [ASTextKitComponents componentsWithAttributedSeedString:attributedString textContainerSize:size]; } - (void)setupTextKitComponentsWithWordKerningDifferentFontSizes @@ -59,7 +59,7 @@ UIFont *normalFont = [UIFont systemFontOfSize:12]; [attributedString addAttribute:NSFontAttributeName value:bigFont range:NSMakeRange(0, 1)]; [attributedString addAttribute:NSFontAttributeName value:normalFont range:NSMakeRange(1, 1)]; - _components = ASTextKitComponentsCreate(attributedString, size); + _components = [ASTextKitComponents componentsWithAttributedSeedString:attributedString textContainerSize:size]; } - (void)testSomeGlyphsToChangeIfWordKerning @@ -76,7 +76,7 @@ UIFont *font = [UIFont systemFontOfSize:12.0]; NSDictionary *attributes = @{NSFontAttributeName : font}; NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@" " attributes:attributes]; - _components = ASTextKitComponentsCreate(attributedString, size); + _components = [ASTextKitComponents componentsWithAttributedSeedString:attributedString textContainerSize:size]; CGFloat expectedWidth = [@" " sizeWithAttributes:@{ NSFontAttributeName : font }].width; CGRect boundingBox = [_layoutManagerDelegate layoutManager:_components.layoutManager boundingBoxForControlGlyphAtIndex:0 forTextContainer:_components.textContainer proposedLineFragment:CGRectZero glyphPosition:CGPointZero characterIndex:0]; @@ -93,7 +93,7 @@ NSDictionary *attributes = @{ASTextNodeWordKerningAttributeName: @(kernValue), NSFontAttributeName : font}; NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@" " attributes:attributes]; - _components = ASTextKitComponentsCreate(attributedString, size); + _components = [ASTextKitComponents componentsWithAttributedSeedString:attributedString textContainerSize:size]; CGFloat expectedWidth = [@" " sizeWithAttributes:@{ NSFontAttributeName : font }].width + kernValue; CGRect boundingBox = [_layoutManagerDelegate layoutManager:_components.layoutManager boundingBoxForControlGlyphAtIndex:0 forTextContainer:_components.textContainer proposedLineFragment:CGRectZero glyphPosition:CGPointZero characterIndex:0]; @@ -137,7 +137,7 @@ NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:seedString attributes:attributes]; UIFont *normalFont = [UIFont systemFontOfSize:12]; [attributedString addAttribute:NSFontAttributeName value:normalFont range:NSMakeRange(0, 1)]; - _components = ASTextKitComponentsCreate(attributedString, size); + _components = [ASTextKitComponents componentsWithAttributedSeedString:attributedString textContainerSize:size]; CGPoint glyphPosition = CGPointMake(42, 54);