From 35d7f43fb6254de62f03c5dc283fbda8b5d229c9 Mon Sep 17 00:00:00 2001 From: Nadine Salter Date: Sun, 25 Jan 2015 15:18:21 -0800 Subject: [PATCH 1/3] Convert ASTextKitComponents to an object. ARC doesn't play nicely with structs that contain references to Objective-C objects, which causes breakage when using AsyncDisplayKit as a dynamic framework (e.g., with CocoaPods 0.36+). Fixes #198. --- AsyncDisplayKit/ASEditableTextNode.mm | 6 +++--- AsyncDisplayKit/Details/ASTextNodeRenderer.mm | 2 +- .../Details/ASTextNodeTextKitHelpers.h | 18 ++++++++++-------- .../Details/ASTextNodeTextKitHelpers.mm | 10 +++++++--- .../ASTextNodeWordKernerTests.mm | 2 +- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index 217fe2677a..70e02b495b 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; @@ -133,7 +133,7 @@ - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { - ASTextKitComponents displayedComponents = [self isDisplayingPlaceholder] ? _placeholderTextKitComponents : _textKitComponents; + ASTextKitComponents *displayedComponents = [self isDisplayingPlaceholder] ? _placeholderTextKitComponents : _textKitComponents; CGSize textSize = ASTextKitComponentsSizeForConstrainedWidth(displayedComponents, 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..2771b4e549 100644 --- a/AsyncDisplayKit/Details/ASTextNodeRenderer.mm +++ b/AsyncDisplayKit/Details/ASTextNodeRenderer.mm @@ -505,7 +505,7 @@ 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 = ASTextKitComponentsCreate(_truncationString, 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..88860971ba 100644 --- a/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.h +++ b/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.h @@ -11,12 +11,14 @@ #import #import -typedef struct { - NSTextStorage *textStorage; - NSTextContainer *textContainer; - NSLayoutManager *layoutManager; - UITextView *textView; -} ASTextKitComponents; +@interface ASTextKitComponents : NSObject + +@property (nonatomic, strong) NSTextStorage *textStorage; +@property (nonatomic, strong) NSTextContainer *textContainer; +@property (nonatomic, strong) NSLayoutManager *layoutManager; +@property (nonatomic, strong) UITextView *textView; + +@end // Convenience. /** @@ -26,11 +28,11 @@ typedef struct { @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. */ -extern ASTextKitComponents ASTextKitComponentsCreate(NSAttributedString *attributedSeedString, CGSize textContainerSize); +extern ASTextKitComponents *ASTextKitComponentsCreate(NSAttributedString *attributedSeedString, 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. */ -extern CGSize ASTextKitComponentsSizeForConstrainedWidth(ASTextKitComponents components, CGFloat constrainedWidth); +extern CGSize ASTextKitComponentsSizeForConstrainedWidth(ASTextKitComponents *components, CGFloat constrainedWidth); diff --git a/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.mm b/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.mm index 2ca5109a46..421c40082b 100644 --- a/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.mm +++ b/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.mm @@ -8,9 +8,13 @@ #import "ASTextNodeTextKitHelpers.h" +@implementation ASTextKitComponents + +@end + #pragma mark - Convenience -CGSize ASTextKitComponentsSizeForConstrainedWidth(ASTextKitComponents components, CGFloat constrainedWidth) +CGSize ASTextKitComponentsSizeForConstrainedWidth(ASTextKitComponents *components, CGFloat constrainedWidth) { // 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`. @@ -25,9 +29,9 @@ CGSize ASTextKitComponentsSizeForConstrainedWidth(ASTextKitComponents components return textSize; } -ASTextKitComponents ASTextKitComponentsCreate(NSAttributedString *attributedSeedString, CGSize textContainerSize) +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]); diff --git a/AsyncDisplayKitTests/ASTextNodeWordKernerTests.mm b/AsyncDisplayKitTests/ASTextNodeWordKernerTests.mm index ad53664620..1aa99966b3 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 From e0f926c86149e426b3765a4dfc26f21e1326fe75 Mon Sep 17 00:00:00 2001 From: Nadine Salter Date: Sun, 25 Jan 2015 16:24:10 -0800 Subject: [PATCH 2/3] Switch ASTextKitComponents interface C -> ObjC. --- AsyncDisplayKit/ASEditableTextNode.mm | 6 +-- AsyncDisplayKit/Details/ASTextNodeRenderer.mm | 3 +- .../Details/ASTextNodeTextKitHelpers.h | 35 ++++++++-------- .../Details/ASTextNodeTextKitHelpers.mm | 41 ++++++++++--------- .../ASTextNodeWordKernerTests.mm | 12 +++--- 5 files changed, 50 insertions(+), 47 deletions(-) diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index 70e02b495b..e5899f51c4 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/AsyncDisplayKit/ASEditableTextNode.mm @@ -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; @@ -134,7 +134,7 @@ - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { ASTextKitComponents *displayedComponents = [self isDisplayingPlaceholder] ? _placeholderTextKitComponents : _textKitComponents; - CGSize textSize = ASTextKitComponentsSizeForConstrainedWidth(displayedComponents, constrainedSize.width); + 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 2771b4e549..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 88860971ba..4c69b0b9b7 100644 --- a/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.h +++ b/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.h @@ -13,26 +13,27 @@ @interface ASTextKitComponents : NSObject +/** + @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. + */ ++ (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. + */ +- (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth; + @property (nonatomic, strong) NSTextStorage *textStorage; @property (nonatomic, strong) NSTextContainer *textContainer; @property (nonatomic, strong) NSLayoutManager *layoutManager; @property (nonatomic, strong) UITextView *textView; @end - -// 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. - */ -extern ASTextKitComponents *ASTextKitComponentsCreate(NSAttributedString *attributedSeedString, 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. - */ -extern CGSize ASTextKitComponentsSizeForConstrainedWidth(ASTextKitComponents *components, CGFloat constrainedWidth); diff --git a/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.mm b/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.mm index 421c40082b..b9c697d6d4 100644 --- a/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.mm +++ b/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.mm @@ -10,26 +10,8 @@ @implementation ASTextKitComponents -@end - -#pragma mark - Convenience - -CGSize ASTextKitComponentsSizeForConstrainedWidth(ASTextKitComponents *components, CGFloat constrainedWidth) -{ - // 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 *)componentsWithAttributedSeedString:(NSAttributedString *)attributedSeedString + textContainerSize:(CGSize)textContainerSize { ASTextKitComponents *components = [[ASTextKitComponents alloc] init]; @@ -45,3 +27,22 @@ ASTextKitComponents *ASTextKitComponentsCreate(NSAttributedString *attributedSee 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 1aa99966b3..994191ed5a 100644 --- a/AsyncDisplayKitTests/ASTextNodeWordKernerTests.mm +++ b/AsyncDisplayKitTests/ASTextNodeWordKernerTests.mm @@ -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); From 241fe8ebb5755e46899b362da42eee0b576f893b Mon Sep 17 00:00:00 2001 From: Nadine Salter Date: Sun, 25 Jan 2015 16:26:07 -0800 Subject: [PATCH 3/3] Further ASTextKitComponents API improvements. --- AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.h | 6 +++--- AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.mm | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.h b/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.h index 4c69b0b9b7..771c8ef775 100644 --- a/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.h +++ b/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.h @@ -31,9 +31,9 @@ */ - (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth; -@property (nonatomic, strong) NSTextStorage *textStorage; -@property (nonatomic, strong) NSTextContainer *textContainer; -@property (nonatomic, strong) NSLayoutManager *layoutManager; +@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 b9c697d6d4..3a6b603c3b 100644 --- a/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.mm +++ b/AsyncDisplayKit/Details/ASTextNodeTextKitHelpers.mm @@ -8,6 +8,15 @@ #import "ASTextNodeTextKitHelpers.h" +@interface ASTextKitComponents () + +// 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