Skip to content
This repository was archived by the owner on Feb 2, 2023. It is now read-only.

Commit 4e80acc

Browse files
leotumwattanaAdlai Holler
authored andcommitted
[ASEditableTextNode] Maximum number of lines to display (#2777) (#2867)
* WIP * Calculate TextKit Height based on max lines to display * Remove TODO * Calculate height based on lineFragmentRect * Fixes issue with calculated width * Resolve TextKit stack threading issues * Removes blank lines * Open brace on next line * setNeedsLayout in case of changes on live node
1 parent daa12a8 commit 4e80acc

5 files changed

Lines changed: 97 additions & 7 deletions

File tree

AsyncDisplayKit.xcodeproj/project.pbxproj

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@
9393
254C6B7D1BF94DF4003EC431 /* ASTextKitShadower.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549F1BEE44CD00737CA5 /* ASTextKitShadower.h */; };
9494
254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */; };
9595
254C6B7F1BF94DF4003EC431 /* ASTextKitTruncating.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */; };
96-
254C6B821BF94F8A003EC431 /* ASTextKitComponents.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitComponents.m */; };
96+
254C6B821BF94F8A003EC431 /* ASTextKitComponents.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitComponents.mm */; };
9797
254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */; };
9898
254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */; };
9999
254C6B851BF94F8A003EC431 /* ASTextKitAttributes.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754941BEE44CD00737CA5 /* ASTextKitAttributes.mm */; };
@@ -112,7 +112,7 @@
112112
257754B01BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549E1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm */; };
113113
257754B21BEE44CD00737CA5 /* ASTextKitShadower.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A01BEE44CD00737CA5 /* ASTextKitShadower.mm */; };
114114
257754B41BEE44CD00737CA5 /* ASTextKitTailTruncater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */; };
115-
257754BE1BEE458E00737CA5 /* ASTextKitComponents.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitComponents.m */; };
115+
257754BE1BEE458E00737CA5 /* ASTextKitComponents.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitComponents.mm */; };
116116
257754BF1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */; };
117117
257754C41BEE458E00737CA5 /* ASTextNodeWordKerner.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */; };
118118
25E327571C16819500A2170C /* ASPagerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 25E327541C16819500A2170C /* ASPagerNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -966,7 +966,7 @@
966966
257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitTailTruncater.h; path = TextKit/ASTextKitTailTruncater.h; sourceTree = "<group>"; };
967967
257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitTailTruncater.mm; path = TextKit/ASTextKitTailTruncater.mm; sourceTree = "<group>"; };
968968
257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitTruncating.h; path = TextKit/ASTextKitTruncating.h; sourceTree = "<group>"; };
969-
257754B71BEE458D00737CA5 /* ASTextKitComponents.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextKitComponents.m; path = TextKit/ASTextKitComponents.m; sourceTree = "<group>"; };
969+
257754B71BEE458D00737CA5 /* ASTextKitComponents.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitComponents.mm; path = TextKit/ASTextKitComponents.mm; sourceTree = "<group>"; };
970970
257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextKitCoreTextAdditions.m; path = TextKit/ASTextKitCoreTextAdditions.m; sourceTree = "<group>"; };
971971
257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextNodeWordKerner.h; path = TextKit/ASTextNodeWordKerner.h; sourceTree = "<group>"; };
972972
257754BA1BEE458E00737CA5 /* ASTextKitComponents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitComponents.h; path = TextKit/ASTextKitComponents.h; sourceTree = "<group>"; };
@@ -1672,7 +1672,7 @@
16721672
B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */,
16731673
B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */,
16741674
257754BA1BEE458E00737CA5 /* ASTextKitComponents.h */,
1675-
257754B71BEE458D00737CA5 /* ASTextKitComponents.m */,
1675+
257754B71BEE458D00737CA5 /* ASTextKitComponents.mm */,
16761676
257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */,
16771677
257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */,
16781678
257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */,
@@ -2305,7 +2305,7 @@
23052305
9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */,
23062306
251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */,
23072307
ACF6ED301B17843500DA7C62 /* ASStackLayoutSpec.mm in Sources */,
2308-
257754BE1BEE458E00737CA5 /* ASTextKitComponents.m in Sources */,
2308+
257754BE1BEE458E00737CA5 /* ASTextKitComponents.mm in Sources */,
23092309
257754A91BEE44CD00737CA5 /* ASTextKitContext.mm in Sources */,
23102310
ACF6ED501B17847A00DA7C62 /* ASStackPositionedLayout.mm in Sources */,
23112311
ACF6ED521B17847A00DA7C62 /* ASStackUnpositionedLayout.mm in Sources */,
@@ -2469,7 +2469,7 @@
24692469
B35062541B010EFD0018CF92 /* ASImageNode+CGExtras.m in Sources */,
24702470
68355B401CB57A69001D4E68 /* ASImageContainerProtocolCategories.m in Sources */,
24712471
B35062031B010EFD0018CF92 /* ASImageNode.mm in Sources */,
2472-
254C6B821BF94F8A003EC431 /* ASTextKitComponents.m in Sources */,
2472+
254C6B821BF94F8A003EC431 /* ASTextKitComponents.mm in Sources */,
24732473
430E7C921B4C23F100697A4C /* ASIndexPath.m in Sources */,
24742474
34EFC7601B701C8B00AD841F /* ASInsetLayoutSpec.mm in Sources */,
24752475
AC6145441D8AFD4F003D62A2 /* ASSection.m in Sources */,

AsyncDisplayKit/ASEditableTextNode.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,24 @@ NS_ASSUME_NONNULL_BEGIN
9292
*/
9393
@property (nonatomic, readwrite) UIEdgeInsets textContainerInset;
9494

95+
/**
96+
@abstract The maximum number of lines to display. Additional lines will require scrolling.
97+
@default 0 (No limit)
98+
*/
99+
@property (nonatomic, assign) NSUInteger maximumLinesToDisplay;
100+
101+
/**
102+
@abstract <UITextInputTraits> properties.
103+
*/
104+
@property(nonatomic, readwrite, assign) UITextAutocapitalizationType autocapitalizationType; // default is UITextAutocapitalizationTypeSentences
105+
@property(nonatomic, readwrite, assign) UITextAutocorrectionType autocorrectionType; // default is UITextAutocorrectionTypeDefault
106+
@property(nonatomic, readwrite, assign) UITextSpellCheckingType spellCheckingType; // default is UITextSpellCheckingTypeDefault;
107+
@property(nonatomic, readwrite, assign) UIKeyboardType keyboardType; // default is UIKeyboardTypeDefault
108+
@property(nonatomic, readwrite, assign) UIKeyboardAppearance keyboardAppearance; // default is UIKeyboardAppearanceDefault
109+
@property(nonatomic, readwrite, assign) UIReturnKeyType returnKeyType; // default is UIReturnKeyDefault (See note under UIReturnKeyType enum)
110+
@property(nonatomic, readwrite, assign) BOOL enablesReturnKeyAutomatically; // default is NO (when YES, will automatically disable return key when text widget has zero-length contents, and will automatically enable when text widget has non-zero-length contents)
111+
@property(nonatomic, readwrite, assign, getter=isSecureTextEntry) BOOL secureTextEntry; // default is NO
112+
95113
/**
96114
@abstract Indicates whether the receiver's text view is the first responder, and thus has the keyboard visible and is prepared for editing by the user.
97115
@result YES if the receiver's text view is the first-responder; NO otherwise.

AsyncDisplayKit/ASEditableTextNode.mm

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,16 @@ - (void)didLoad
238238
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
239239
{
240240
ASTextKitComponents *displayedComponents = [self isDisplayingPlaceholder] ? _placeholderTextKitComponents : _textKitComponents;
241-
CGSize textSize = [displayedComponents sizeForConstrainedWidth:constrainedSize.width];
241+
242+
CGSize textSize;
243+
244+
if (_maximumLinesToDisplay > 0) {
245+
textSize = [displayedComponents sizeForConstrainedWidth:constrainedSize.width
246+
forMaxNumberOfLines: _maximumLinesToDisplay];
247+
} else {
248+
textSize = [displayedComponents sizeForConstrainedWidth:constrainedSize.width];
249+
}
250+
242251
CGFloat width = std::ceil(textSize.width + _textContainerInset.left + _textContainerInset.right);
243252
CGFloat height = std::ceil(textSize.height + _textContainerInset.top + _textContainerInset.bottom);
244253
return CGSizeMake(std::fmin(width, constrainedSize.width), std::fmin(height, constrainedSize.height));
@@ -313,6 +322,12 @@ - (UITextView *)textView
313322
return _textKitComponents.textView;
314323
}
315324

325+
- (void)setMaximumLinesToDisplay:(NSUInteger)maximumLines
326+
{
327+
_maximumLinesToDisplay = maximumLines;
328+
[self setNeedsLayout];
329+
}
330+
316331
#pragma mark -
317332
@dynamic typingAttributes;
318333

AsyncDisplayKit/TextKit/ASTextKitComponents.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ NS_ASSUME_NONNULL_BEGIN
4444
*/
4545
- (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth;
4646

47+
48+
- (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth
49+
forMaxNumberOfLines:(NSInteger)numberOfLines;
50+
4751
@property (nonatomic, strong, readonly) NSTextStorage *textStorage;
4852
@property (nonatomic, strong, readonly) NSTextContainer *textContainer;
4953
@property (nonatomic, strong, readonly) NSLayoutManager *layoutManager;

AsyncDisplayKit/TextKit/ASTextKitComponents.m renamed to AsyncDisplayKit/TextKit/ASTextKitComponents.mm

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
#import "ASTextKitComponents.h"
1212

13+
#import <tgmath.h>
14+
1315
@interface ASTextKitComponents ()
1416

1517
// read-write redeclarations
@@ -66,4 +68,55 @@ - (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth
6668
return textSize;
6769
}
6870

71+
- (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth
72+
forMaxNumberOfLines:(NSInteger)maxNumberOfLines
73+
{
74+
if (maxNumberOfLines == 0) {
75+
return [self sizeForConstrainedWidth:constrainedWidth];
76+
}
77+
78+
ASTextKitComponents *components = self;
79+
80+
// Always use temporary stack in case of threading issues
81+
components = [ASTextKitComponents componentsWithAttributedSeedString:components.textStorage textContainerSize:CGSizeMake(constrainedWidth, CGFLOAT_MAX)];
82+
83+
// Force glyph generation and layout, which may not have happened yet (and isn't triggered by - usedRectForTextContainer:).
84+
[components.layoutManager ensureLayoutForTextContainer:components.textContainer];
85+
86+
CGFloat width = [components.layoutManager usedRectForTextContainer:components.textContainer].size.width;
87+
88+
// Calculate height based on line fragments
89+
// Based on calculating number of lines from: http://asciiwwdc.com/2013/sessions/220
90+
NSRange glyphRange, lineRange = NSMakeRange(0, 0);
91+
CGRect rect = CGRectZero;
92+
CGFloat height = 0;
93+
CGFloat lastOriginY = -1.0;
94+
NSUInteger numberOfLines = 0;
95+
96+
glyphRange = [components.layoutManager glyphRangeForTextContainer:components.textContainer];
97+
98+
while (lineRange.location < NSMaxRange(glyphRange)) {
99+
rect = [components.layoutManager lineFragmentRectForGlyphAtIndex:lineRange.location
100+
effectiveRange:&lineRange];
101+
102+
if (CGRectGetMinY(rect) > lastOriginY) {
103+
++numberOfLines;
104+
if (numberOfLines == maxNumberOfLines) {
105+
height = rect.origin.y + rect.size.height;
106+
break;
107+
}
108+
}
109+
110+
lastOriginY = CGRectGetMinY(rect);
111+
lineRange.location = NSMaxRange(lineRange);
112+
}
113+
114+
CGFloat fragmentHeight = rect.origin.y + rect.size.height;
115+
CGFloat finalHeight = std::ceil(std::fmax(height, fragmentHeight));
116+
117+
CGSize size = CGSizeMake(width, finalHeight);
118+
119+
return size;
120+
}
121+
69122
@end

0 commit comments

Comments
 (0)