Add support for textContainerInset to ASTextNode (ala UITextView) (#2062)

* Add support for textContainerInset to ASTextNode (ala UITextView)

* Better comment, parens to increase readability. Thanks @schneider!

* Add textContainerInset snapshot test.
This commit is contained in:
Garrett Moon 2016-08-12 12:07:00 -07:00 committed by Adlai Holler
parent 39cb188b9e
commit 2c9e51e8f7
4 changed files with 95 additions and 4 deletions

View File

@ -31,6 +31,14 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
@property (nullable, nonatomic, copy) NSTextStorage * (^textStorageCreationBlock)(NSAttributedString *_Nullable attributedString); @property (nullable, nonatomic, copy) NSTextStorage * (^textStorageCreationBlock)(NSAttributedString *_Nullable attributedString);
/**
@abstract Text margins for text laid out in the text node.
@discussion defaults to UIEdgeInsetsZero.
This property can be useful for handling text which does not fit within the view by default. An example: like UILabel,
ASTextNode will clip the left and right of the string "judar" if it's rendered in an italicised font.
*/
@property (nonatomic, assign) UIEdgeInsets textContainerInset;
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

View File

@ -49,6 +49,8 @@ struct ASTextNodeDrawParameter {
CGFloat _shadowOpacity; CGFloat _shadowOpacity;
CGFloat _shadowRadius; CGFloat _shadowRadius;
UIEdgeInsets _textContainerInset;
NSArray *_exclusionPaths; NSArray *_exclusionPaths;
NSAttributedString *_composedTruncationText; NSAttributedString *_composedTruncationText;
@ -213,7 +215,15 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
if (_renderer == nil) { if (_renderer == nil) {
CGSize constrainedSize = _constrainedSize.width != -INFINITY ? _constrainedSize : bounds.size; CGSize constrainedSize;
if (_constrainedSize.width != -INFINITY) {
constrainedSize = _constrainedSize;
} else {
constrainedSize = bounds.size;
constrainedSize.width -= (_textContainerInset.left + _textContainerInset.right);
constrainedSize.height -= (_textContainerInset.top + _textContainerInset.bottom);
}
_renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:[self _rendererAttributes] _renderer = [[ASTextKitRenderer alloc] initWithTextKitAttributes:[self _rendererAttributes]
constrainedSize:constrainedSize]; constrainedSize:constrainedSize];
} }
@ -279,6 +289,24 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
#pragma mark - Layout and Sizing #pragma mark - Layout and Sizing
- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset
{
ASDN::MutexLocker l(__instanceLock__);
BOOL needsUpdate = !UIEdgeInsetsEqualToEdgeInsets(textContainerInset, _textContainerInset);
if (needsUpdate) {
_textContainerInset = textContainerInset;
[self invalidateCalculatedLayout];
[self setNeedsLayout];
}
}
- (UIEdgeInsets)textContainerInset
{
ASDN::MutexLocker l(__instanceLock__);
return _textContainerInset;
}
- (BOOL)_needInvalidateRendererForBoundsSize:(CGSize)boundsSize - (BOOL)_needInvalidateRendererForBoundsSize:(CGSize)boundsSize
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
@ -291,6 +319,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
// a new one. However, there are common cases where the constrained size doesn't need to be the same as calculated. // a new one. However, there are common cases where the constrained size doesn't need to be the same as calculated.
CGSize rendererConstrainedSize = _renderer.constrainedSize; CGSize rendererConstrainedSize = _renderer.constrainedSize;
//inset bounds
boundsSize.width -= _textContainerInset.left + _textContainerInset.right;
boundsSize.height -= _textContainerInset.top + _textContainerInset.bottom;
if (CGSizeEqualToSize(boundsSize, rendererConstrainedSize)) { if (CGSizeEqualToSize(boundsSize, rendererConstrainedSize)) {
return NO; return NO;
} else { } else {
@ -321,9 +353,14 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
if (layout != nil) { if (layout != nil) {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
if (CGSizeEqualToSize(_constrainedSize, layout.size) == NO) { CGSize layoutSize = layout.size;
_constrainedSize = layout.size; //Apply textContainerInset
_renderer.constrainedSize = layout.size; layoutSize.width -= (_textContainerInset.left + _textContainerInset.right);
layoutSize.height -= (_textContainerInset.top + _textContainerInset.bottom);
if (CGSizeEqualToSize(_constrainedSize, layoutSize) == NO) {
_constrainedSize = layoutSize;
_renderer.constrainedSize = layoutSize;
} }
} }
} }
@ -335,6 +372,10 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
//remove textContainerInset
constrainedSize.width -= (_textContainerInset.left + _textContainerInset.right);
constrainedSize.height -= (_textContainerInset.top + _textContainerInset.bottom);
_constrainedSize = constrainedSize; _constrainedSize = constrainedSize;
// Instead of invalidating the renderer, in case this is a new call with a different constrained size, // Instead of invalidating the renderer, in case this is a new call with a different constrained size,
@ -353,6 +394,11 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
self.descender *= _renderer.currentScaleFactor; self.descender *= _renderer.currentScaleFactor;
} }
} }
//add textContainerInset
size.width += (_textContainerInset.left + _textContainerInset.right);
size.height += (_textContainerInset.top + _textContainerInset.bottom);
return size; return size;
} }
@ -466,6 +512,8 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
CGContextSaveGState(context); CGContextSaveGState(context);
CGContextTranslateCTM(context, _textContainerInset.left, _textContainerInset.top);
ASTextKitRenderer *renderer = [self _rendererWithBounds:drawParameterBounds]; ASTextKitRenderer *renderer = [self _rendererWithBounds:drawParameterBounds];
UIEdgeInsets shadowPadding = [self shadowPaddingWithRenderer:renderer]; UIEdgeInsets shadowPadding = [self shadowPaddingWithRenderer:renderer];
CGPoint boundsOrigin = drawParameterBounds.origin; CGPoint boundsOrigin = drawParameterBounds.origin;

View File

@ -0,0 +1,35 @@
//
// ASTextNodeSnapshotTests.m
// AsyncDisplayKit
//
// Created by Garrett Moon on 8/12/16.
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import "ASSnapshotTestCase.h"
#import <AsyncDisplayKit/AsyncDisplayKit.h>
@interface ASTextNodeSnapshotTests : ASSnapshotTestCase
@end
@implementation ASTextNodeSnapshotTests
- (void)testTextContainerInset
{
// trivial test case to ensure ASSnapshotTestCase works
ASTextNode *textNode = [[ASTextNode alloc] init];
textNode.attributedText = [[NSAttributedString alloc] initWithString:@"judar"
attributes:@{NSFontAttributeName : [UIFont italicSystemFontOfSize:24]}];
[textNode measureWithSizeRange:ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))];
textNode.frame = CGRectMake(0, 0, textNode.calculatedSize.width, textNode.calculatedSize.height);
textNode.textContainerInset = UIEdgeInsetsMake(0, 2, 0, 2);
ASSnapshotVerifyNode(textNode, nil);
}
@end

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB