diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 4b6463ff2e..dbbd53b5cc 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -93,7 +93,7 @@ 254C6B7D1BF94DF4003EC431 /* ASTextKitShadower.h in Headers */ = {isa = PBXBuildFile; fileRef = 2577549F1BEE44CD00737CA5 /* ASTextKitShadower.h */; }; 254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */; }; 254C6B7F1BF94DF4003EC431 /* ASTextKitTruncating.h in Headers */ = {isa = PBXBuildFile; fileRef = 257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */; }; - 254C6B821BF94F8A003EC431 /* ASTextKitComponents.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitComponents.m */; }; + 254C6B821BF94F8A003EC431 /* ASTextKitComponents.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitComponents.mm */; }; 254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */; }; 254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */; }; 254C6B851BF94F8A003EC431 /* ASTextKitAttributes.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754941BEE44CD00737CA5 /* ASTextKitAttributes.mm */; }; @@ -112,7 +112,7 @@ 257754B01BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2577549E1BEE44CD00737CA5 /* ASTextKitRenderer+TextChecking.mm */; }; 257754B21BEE44CD00737CA5 /* ASTextKitShadower.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A01BEE44CD00737CA5 /* ASTextKitShadower.mm */; }; 257754B41BEE44CD00737CA5 /* ASTextKitTailTruncater.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */; }; - 257754BE1BEE458E00737CA5 /* ASTextKitComponents.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitComponents.m */; }; + 257754BE1BEE458E00737CA5 /* ASTextKitComponents.mm in Sources */ = {isa = PBXBuildFile; fileRef = 257754B71BEE458D00737CA5 /* ASTextKitComponents.mm */; }; 257754BF1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */; }; 257754C41BEE458E00737CA5 /* ASTextNodeWordKerner.m in Sources */ = {isa = PBXBuildFile; fileRef = 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */; }; 25E327571C16819500A2170C /* ASPagerNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 25E327541C16819500A2170C /* ASPagerNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -966,7 +966,7 @@ 257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitTailTruncater.h; path = TextKit/ASTextKitTailTruncater.h; sourceTree = ""; }; 257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitTailTruncater.mm; path = TextKit/ASTextKitTailTruncater.mm; sourceTree = ""; }; 257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitTruncating.h; path = TextKit/ASTextKitTruncating.h; sourceTree = ""; }; - 257754B71BEE458D00737CA5 /* ASTextKitComponents.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextKitComponents.m; path = TextKit/ASTextKitComponents.m; sourceTree = ""; }; + 257754B71BEE458D00737CA5 /* ASTextKitComponents.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASTextKitComponents.mm; path = TextKit/ASTextKitComponents.mm; sourceTree = ""; }; 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASTextKitCoreTextAdditions.m; path = TextKit/ASTextKitCoreTextAdditions.m; sourceTree = ""; }; 257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextNodeWordKerner.h; path = TextKit/ASTextNodeWordKerner.h; sourceTree = ""; }; 257754BA1BEE458E00737CA5 /* ASTextKitComponents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASTextKitComponents.h; path = TextKit/ASTextKitComponents.h; sourceTree = ""; }; @@ -1672,7 +1672,7 @@ B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */, B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */, 257754BA1BEE458E00737CA5 /* ASTextKitComponents.h */, - 257754B71BEE458D00737CA5 /* ASTextKitComponents.m */, + 257754B71BEE458D00737CA5 /* ASTextKitComponents.mm */, 257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */, 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */, 257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */, @@ -2305,7 +2305,7 @@ 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */, 251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */, ACF6ED301B17843500DA7C62 /* ASStackLayoutSpec.mm in Sources */, - 257754BE1BEE458E00737CA5 /* ASTextKitComponents.m in Sources */, + 257754BE1BEE458E00737CA5 /* ASTextKitComponents.mm in Sources */, 257754A91BEE44CD00737CA5 /* ASTextKitContext.mm in Sources */, ACF6ED501B17847A00DA7C62 /* ASStackPositionedLayout.mm in Sources */, ACF6ED521B17847A00DA7C62 /* ASStackUnpositionedLayout.mm in Sources */, @@ -2469,7 +2469,7 @@ B35062541B010EFD0018CF92 /* ASImageNode+CGExtras.m in Sources */, 68355B401CB57A69001D4E68 /* ASImageContainerProtocolCategories.m in Sources */, B35062031B010EFD0018CF92 /* ASImageNode.mm in Sources */, - 254C6B821BF94F8A003EC431 /* ASTextKitComponents.m in Sources */, + 254C6B821BF94F8A003EC431 /* ASTextKitComponents.mm in Sources */, 430E7C921B4C23F100697A4C /* ASIndexPath.m in Sources */, 34EFC7601B701C8B00AD841F /* ASInsetLayoutSpec.mm in Sources */, AC6145441D8AFD4F003D62A2 /* ASSection.m in Sources */, diff --git a/AsyncDisplayKit/ASEditableTextNode.h b/AsyncDisplayKit/ASEditableTextNode.h index b74f8e3d1a..2d95e24496 100644 --- a/AsyncDisplayKit/ASEditableTextNode.h +++ b/AsyncDisplayKit/ASEditableTextNode.h @@ -92,6 +92,24 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, readwrite) UIEdgeInsets textContainerInset; +/** + @abstract The maximum number of lines to display. Additional lines will require scrolling. + @default 0 (No limit) + */ +@property (nonatomic, assign) NSUInteger maximumLinesToDisplay; + +/** + @abstract properties. + */ +@property(nonatomic, readwrite, assign) UITextAutocapitalizationType autocapitalizationType; // default is UITextAutocapitalizationTypeSentences +@property(nonatomic, readwrite, assign) UITextAutocorrectionType autocorrectionType; // default is UITextAutocorrectionTypeDefault +@property(nonatomic, readwrite, assign) UITextSpellCheckingType spellCheckingType; // default is UITextSpellCheckingTypeDefault; +@property(nonatomic, readwrite, assign) UIKeyboardType keyboardType; // default is UIKeyboardTypeDefault +@property(nonatomic, readwrite, assign) UIKeyboardAppearance keyboardAppearance; // default is UIKeyboardAppearanceDefault +@property(nonatomic, readwrite, assign) UIReturnKeyType returnKeyType; // default is UIReturnKeyDefault (See note under UIReturnKeyType enum) +@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) +@property(nonatomic, readwrite, assign, getter=isSecureTextEntry) BOOL secureTextEntry; // default is NO + /** @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. @result YES if the receiver's text view is the first-responder; NO otherwise. diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index 4fd2dfdc8f..8507189bdf 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/AsyncDisplayKit/ASEditableTextNode.mm @@ -238,7 +238,16 @@ - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { ASTextKitComponents *displayedComponents = [self isDisplayingPlaceholder] ? _placeholderTextKitComponents : _textKitComponents; - CGSize textSize = [displayedComponents sizeForConstrainedWidth:constrainedSize.width]; + + CGSize textSize; + + if (_maximumLinesToDisplay > 0) { + textSize = [displayedComponents sizeForConstrainedWidth:constrainedSize.width + forMaxNumberOfLines: _maximumLinesToDisplay]; + } else { + textSize = [displayedComponents sizeForConstrainedWidth:constrainedSize.width]; + } + CGFloat width = std::ceil(textSize.width + _textContainerInset.left + _textContainerInset.right); CGFloat height = std::ceil(textSize.height + _textContainerInset.top + _textContainerInset.bottom); return CGSizeMake(std::fmin(width, constrainedSize.width), std::fmin(height, constrainedSize.height)); @@ -313,6 +322,12 @@ return _textKitComponents.textView; } +- (void)setMaximumLinesToDisplay:(NSUInteger)maximumLines +{ + _maximumLinesToDisplay = maximumLines; + [self setNeedsLayout]; +} + #pragma mark - @dynamic typingAttributes; diff --git a/AsyncDisplayKit/TextKit/ASTextKitComponents.h b/AsyncDisplayKit/TextKit/ASTextKitComponents.h index 9da19d7061..5cdad469f3 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitComponents.h +++ b/AsyncDisplayKit/TextKit/ASTextKitComponents.h @@ -44,6 +44,10 @@ NS_ASSUME_NONNULL_BEGIN */ - (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth; + +- (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth + forMaxNumberOfLines:(NSInteger)numberOfLines; + @property (nonatomic, strong, readonly) NSTextStorage *textStorage; @property (nonatomic, strong, readonly) NSTextContainer *textContainer; @property (nonatomic, strong, readonly) NSLayoutManager *layoutManager; diff --git a/AsyncDisplayKit/TextKit/ASTextKitComponents.m b/AsyncDisplayKit/TextKit/ASTextKitComponents.mm similarity index 59% rename from AsyncDisplayKit/TextKit/ASTextKitComponents.m rename to AsyncDisplayKit/TextKit/ASTextKitComponents.mm index 93eb429ba4..e78b67823a 100644 --- a/AsyncDisplayKit/TextKit/ASTextKitComponents.m +++ b/AsyncDisplayKit/TextKit/ASTextKitComponents.mm @@ -10,6 +10,8 @@ #import "ASTextKitComponents.h" +#import + @interface ASTextKitComponents () // read-write redeclarations @@ -66,4 +68,55 @@ return textSize; } +- (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth + forMaxNumberOfLines:(NSInteger)maxNumberOfLines +{ + if (maxNumberOfLines == 0) { + return [self sizeForConstrainedWidth:constrainedWidth]; + } + + ASTextKitComponents *components = self; + + // Always use temporary stack in case of threading issues + components = [ASTextKitComponents componentsWithAttributedSeedString:components.textStorage textContainerSize:CGSizeMake(constrainedWidth, CGFLOAT_MAX)]; + + // Force glyph generation and layout, which may not have happened yet (and isn't triggered by - usedRectForTextContainer:). + [components.layoutManager ensureLayoutForTextContainer:components.textContainer]; + + CGFloat width = [components.layoutManager usedRectForTextContainer:components.textContainer].size.width; + + // Calculate height based on line fragments + // Based on calculating number of lines from: http://asciiwwdc.com/2013/sessions/220 + NSRange glyphRange, lineRange = NSMakeRange(0, 0); + CGRect rect = CGRectZero; + CGFloat height = 0; + CGFloat lastOriginY = -1.0; + NSUInteger numberOfLines = 0; + + glyphRange = [components.layoutManager glyphRangeForTextContainer:components.textContainer]; + + while (lineRange.location < NSMaxRange(glyphRange)) { + rect = [components.layoutManager lineFragmentRectForGlyphAtIndex:lineRange.location + effectiveRange:&lineRange]; + + if (CGRectGetMinY(rect) > lastOriginY) { + ++numberOfLines; + if (numberOfLines == maxNumberOfLines) { + height = rect.origin.y + rect.size.height; + break; + } + } + + lastOriginY = CGRectGetMinY(rect); + lineRange.location = NSMaxRange(lineRange); + } + + CGFloat fragmentHeight = rect.origin.y + rect.size.height; + CGFloat finalHeight = std::ceil(std::fmax(height, fragmentHeight)); + + CGSize size = CGSizeMake(width, finalHeight); + + return size; +} + @end