[ASTextNode] Optimize handling of constrained size to almost never recreate NSLayoutManager

This also fixes two fairly subtle but serious bugs, #1076 and #1046.
This commit is contained in:
Scott Goodson
2016-01-24 00:50:43 -08:00
parent 82f7956bf9
commit 9ddf68fa96
9 changed files with 81 additions and 55 deletions

View File

@@ -30,6 +30,8 @@
constrainedSize:(CGSize)constrainedSize
layoutManagerFactory:(NSLayoutManager*(*)(void))layoutManagerFactory;
@property (nonatomic, assign, readwrite) CGSize constrainedSize;
/**
All operations on TextKit values MUST occur within this locked context. Simultaneous access (even non-mutative) to
TextKit components may cause crashes.

View File

@@ -49,6 +49,16 @@
return self;
}
- (CGSize)constrainedSize
{
return _textContainer.size;
}
- (void)setConstrainedSize:(CGSize)constrainedSize
{
_textContainer.size = constrainedSize;
}
- (void)performBlockWithLockedTextKitComponents:(void (^)(NSLayoutManager *,
NSTextStorage *,
NSTextContainer *))block

View File

@@ -37,7 +37,6 @@
/**
Designated Initializer
dvlkferufedgjnhjjfhldjedlunvtdtv
@discussion Sizing will occur as a result of initialization, so be careful when/where you use this.
*/
- (instancetype)initWithTextKitAttributes:(const ASTextKitAttributes &)textComponentAttributes
@@ -51,7 +50,7 @@ dvlkferufedgjnhjjfhldjedlunvtdtv
@property (nonatomic, assign, readonly) ASTextKitAttributes attributes;
@property (nonatomic, assign, readonly) CGSize constrainedSize;
@property (nonatomic, assign, readwrite) CGSize constrainedSize;
#pragma mark - Drawing
/*

View File

@@ -17,6 +17,9 @@
#import "ASTextKitTailTruncater.h"
#import "ASTextKitTruncating.h"
//#define LOG(...) NSLog(__VA_ARGS__)
#define LOG(...)
static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
{
static NSCharacterSet *truncationCharacterSet;
@@ -65,12 +68,10 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
{
if (!_truncater) {
ASTextKitAttributes attributes = _attributes;
// We must inset the constrained size by the size of the shadower.
CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize];
NSCharacterSet *avoidTailTruncationSet = attributes.avoidTailTruncationSet ? : _defaultAvoidTruncationCharacterSet();
_truncater = [[ASTextKitTailTruncater alloc] initWithContext:[self context]
truncationAttributedString:attributes.truncationAttributedString
avoidTailTruncationSet:attributes.avoidTailTruncationSet ?: _defaultAvoidTruncationCharacterSet()
constrainedSize:shadowConstrainedSize];
avoidTailTruncationSet:avoidTailTruncationSet];
}
return _truncater;
}
@@ -79,6 +80,7 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
{
if (!_context) {
ASTextKitAttributes attributes = _attributes;
// We must inset the constrained size by the size of the shadower.
CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:_constrainedSize];
_context = [[ASTextKitContext alloc] initWithAttributedString:attributes.attributedString
lineBreakMode:attributes.lineBreakMode
@@ -92,6 +94,30 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
#pragma mark - Sizing
- (CGSize)size
{
if (!_sizeIsCalculated) {
[self _calculateSize];
_sizeIsCalculated = YES;
}
return _calculatedSize;
}
- (void)setConstrainedSize:(CGSize)constrainedSize
{
if (!CGSizeEqualToSize(constrainedSize, _constrainedSize)) {
_sizeIsCalculated = NO;
_constrainedSize = constrainedSize;
// If the context isn't created yet, it will be initialized with the appropriate size when next accessed.
if (_context) {
// If we're updating an existing context, make sure to use the same inset logic used during initialization.
// This codepath allows us to reuse the
CGSize shadowConstrainedSize = [[self shadower] insetSizeWithConstrainedSize:constrainedSize];
_context.constrainedSize = shadowConstrainedSize;
}
}
}
- (void)_calculateSize
{
// Force glyph generation and layout, which may not have happened yet (and isn't triggered by
@@ -111,16 +137,7 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
// to make sure our width calculations aren't being offset by glyphs going beyond the constrained rect.
boundingRect = CGRectIntersection(boundingRect, {.size = constrainedRect.size});
_calculatedSize = [_shadower outsetSizeWithInsetSize:CGSizeMake(boundingRect.size.width + boundingRect.origin.x, boundingRect.size.height + boundingRect.origin.y)];
}
- (CGSize)size
{
if (!_sizeIsCalculated) {
[self _calculateSize];
_sizeIsCalculated = YES;
}
return _calculatedSize;
_calculatedSize = [_shadower outsetSizeWithInsetSize:boundingRect.size];
}
#pragma mark - Drawing
@@ -136,8 +153,12 @@ static NSCharacterSet *_defaultAvoidTruncationCharacterSet()
[[self shadower] setShadowInContext:context];
UIGraphicsPushContext(context);
LOG(@"%@, shadowInsetBounds = %@",self, NSStringFromCGRect(shadowInsetBounds));
[[self context] performBlockWithLockedTextKitComponents:^(NSLayoutManager *layoutManager, NSTextStorage *textStorage, NSTextContainer *textContainer) {
LOG(@"usedRect: %@", NSStringFromCGRect([layoutManager usedRectForTextContainer:textContainer]));
NSRange glyphRange = [layoutManager glyphRangeForTextContainer:textContainer];
LOG(@"boundingRect: %@", NSStringFromCGRect([layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer]));
[layoutManager drawBackgroundForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
[layoutManager drawGlyphsForGlyphRange:glyphRange atPoint:shadowInsetBounds.origin];
}];

View File

@@ -18,7 +18,6 @@
__weak ASTextKitContext *_context;
NSAttributedString *_truncationAttributedString;
NSCharacterSet *_avoidTailTruncationSet;
CGSize _constrainedSize;
}
@synthesize visibleRanges = _visibleRanges;
@synthesize truncationStringRect = _truncationStringRect;
@@ -26,13 +25,11 @@
- (instancetype)initWithContext:(ASTextKitContext *)context
truncationAttributedString:(NSAttributedString *)truncationAttributedString
avoidTailTruncationSet:(NSCharacterSet *)avoidTailTruncationSet
constrainedSize:(CGSize)constrainedSize
{
if (self = [super init]) {
_context = context;
_truncationAttributedString = truncationAttributedString;
_avoidTailTruncationSet = avoidTailTruncationSet;
_constrainedSize = constrainedSize;
[self _truncate];
}

View File

@@ -31,7 +31,6 @@
*/
- (instancetype)initWithContext:(ASTextKitContext *)context
truncationAttributedString:(NSAttributedString *)truncationAttributedString
avoidTailTruncationSet:(NSCharacterSet *)avoidTailTruncationSet
constrainedSize:(CGSize)constrainedSize;
avoidTailTruncationSet:(NSCharacterSet *)avoidTailTruncationSet;
@end