From 9ff8aff9be1525d0bf61b8ede86a46c6d81d07d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiancheng/=E5=BE=90=E4=B8=89?= <113859954+yaotiancheng-ola@users.noreply.github.com> Date: Wed, 8 Apr 2026 11:32:04 +0800 Subject: [PATCH 1/5] Add Package.swift for YYText library --- Package.swift | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Package.swift diff --git a/Package.swift b/Package.swift new file mode 100644 index 00000000..9b39cfa8 --- /dev/null +++ b/Package.swift @@ -0,0 +1,33 @@ +// swift-tools-version:5.7 +import PackageDescription + +let package = Package( + name: "YYText", + platforms: [ + .iOS(.v12) + ], + products: [ + .library( + name: "YYText", + targets: ["YYText"] + ) + ], + targets: [ + .target( + name: "YYText", + path: "YYText", + publicHeadersPath: "include", + cSettings: [ + .define("YYTEXT_ENABLE_SHORTHAND", to: "1") + ], + linkerSettings: [ + .linkedFramework("UIKit"), + .linkedFramework("CoreFoundation"), + .linkedFramework("CoreText"), + .linkedFramework("QuartzCore"), + .linkedFramework("Accelerate"), + .linkedFramework("MobileCoreServices") + ] + ) + ] +) From f7a1e8eb0311ffa2714121134202f7f1df9568da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiancheng/=E5=BE=90=E4=B8=89?= <113859954+yaotiancheng-ola@users.noreply.github.com> Date: Wed, 8 Apr 2026 11:33:21 +0800 Subject: [PATCH 2/5] Update .gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 01875062..c5a201c9 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,7 @@ fastlane/test_output # https://github.com/johnno1962/injectionforxcode iOSInjectionProject/ + +# SPM +Package.resolved +.build/ From 36751f775c6717fd3628c1bffd7b01efd7788a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiancheng/=E5=BE=90=E4=B8=89?= <113859954+yaotiancheng-ola@users.noreply.github.com> Date: Wed, 8 Apr 2026 11:39:01 +0800 Subject: [PATCH 3/5] Update Package.swift Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 9b39cfa8..6330ebfb 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,7 @@ let package = Package( .target( name: "YYText", path: "YYText", - publicHeadersPath: "include", + publicHeadersPath: ".", cSettings: [ .define("YYTEXT_ENABLE_SHORTHAND", to: "1") ], From 5d988b3183450813a2d6d917a351f4bd0d32f9d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiancheng/=E5=BE=90=E4=B8=89?= <113859954+yaotiancheng-ola@users.noreply.github.com> Date: Wed, 8 Apr 2026 11:39:09 +0800 Subject: [PATCH 4/5] Update Package.swift Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 6330ebfb..7c2f356f 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,7 @@ import PackageDescription let package = Package( name: "YYText", platforms: [ - .iOS(.v12) + .iOS(.v9) ], products: [ .library( From 565252cca4ad270969b96085f7c8c109dc21f3f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E4=B8=89?= Date: Wed, 8 Apr 2026 11:48:30 +0800 Subject: [PATCH 5/5] =?UTF-8?q?feat:=20=E8=B0=83=E6=95=B4spm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Package.swift | 11 +- YYText/include/NSAttributedString+YYText.h | 1415 ++++++++++++++++++++ YYText/include/NSParagraphStyle+YYText.h | 37 + YYText/include/UIPasteboard+YYText.h | 41 + YYText/include/UIView+YYText.h | 72 + YYText/include/YYLabel.h | 380 ++++++ YYText/include/YYText.h | 50 + YYText/include/YYTextArchiver.h | 33 + YYText/include/YYTextAsyncLayer.h | 79 ++ YYText/include/YYTextAttribute.h | 347 +++++ YYText/include/YYTextContainerView.h | 55 + YYText/include/YYTextDebugOption.h | 95 ++ YYText/include/YYTextEffectWindow.h | 52 + YYText/include/YYTextInput.h | 87 ++ YYText/include/YYTextKeyboardManager.h | 98 ++ YYText/include/YYTextLayout.h | 571 ++++++++ YYText/include/YYTextLine.h | 84 ++ YYText/include/YYTextMagnifier.h | 52 + YYText/include/YYTextParser.h | 91 ++ YYText/include/YYTextRubyAnnotation.h | 78 ++ YYText/include/YYTextRunDelegate.h | 68 + YYText/include/YYTextSelectionView.h | 78 ++ YYText/include/YYTextTransaction.h | 42 + YYText/include/YYTextUtilities.h | 563 ++++++++ YYText/include/YYTextView.h | 410 ++++++ YYText/include/YYTextWeakProxy.h | 61 + 26 files changed, 4948 insertions(+), 2 deletions(-) create mode 100644 YYText/include/NSAttributedString+YYText.h create mode 100644 YYText/include/NSParagraphStyle+YYText.h create mode 100644 YYText/include/UIPasteboard+YYText.h create mode 100644 YYText/include/UIView+YYText.h create mode 100644 YYText/include/YYLabel.h create mode 100644 YYText/include/YYText.h create mode 100644 YYText/include/YYTextArchiver.h create mode 100644 YYText/include/YYTextAsyncLayer.h create mode 100644 YYText/include/YYTextAttribute.h create mode 100644 YYText/include/YYTextContainerView.h create mode 100644 YYText/include/YYTextDebugOption.h create mode 100644 YYText/include/YYTextEffectWindow.h create mode 100644 YYText/include/YYTextInput.h create mode 100644 YYText/include/YYTextKeyboardManager.h create mode 100644 YYText/include/YYTextLayout.h create mode 100644 YYText/include/YYTextLine.h create mode 100644 YYText/include/YYTextMagnifier.h create mode 100644 YYText/include/YYTextParser.h create mode 100644 YYText/include/YYTextRubyAnnotation.h create mode 100644 YYText/include/YYTextRunDelegate.h create mode 100644 YYText/include/YYTextSelectionView.h create mode 100644 YYText/include/YYTextTransaction.h create mode 100644 YYText/include/YYTextUtilities.h create mode 100644 YYText/include/YYTextView.h create mode 100644 YYText/include/YYTextWeakProxy.h diff --git a/Package.swift b/Package.swift index 7c2f356f..9ad160fb 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,7 @@ import PackageDescription let package = Package( name: "YYText", platforms: [ - .iOS(.v9) + .iOS(.v12) ], products: [ .library( @@ -16,7 +16,14 @@ let package = Package( .target( name: "YYText", path: "YYText", - publicHeadersPath: ".", + sources: [ + "YYLabel.m", + "YYTextView.m", + "Component", + "String", + "Utility" + ], + publicHeadersPath: "include", cSettings: [ .define("YYTEXT_ENABLE_SHORTHAND", to: "1") ], diff --git a/YYText/include/NSAttributedString+YYText.h b/YYText/include/NSAttributedString+YYText.h new file mode 100644 index 00000000..84722fb7 --- /dev/null +++ b/YYText/include/NSAttributedString+YYText.h @@ -0,0 +1,1415 @@ +// +// NSAttributedString+YYText.h +// YYText +// +// Created by ibireme on 14/10/7. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import +#import + +#if __has_include() +#import +#import +#else +#import "YYTextAttribute.h" +#import "YYTextRubyAnnotation.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + Get pre-defined attributes from attributed string. + All properties defined in UIKit, CoreText and YYText are included. + */ +@interface NSAttributedString (YYText) + +/** + Archive the string to data. + @return Returns nil if an error occurs. + */ +- (nullable NSData *)yy_archiveToData; + +/** + Unarchive string from data. + @param data The archived attributed string data. + @return Returns nil if an error occurs. + */ ++ (nullable instancetype)yy_unarchiveFromData:(NSData *)data; + + + +#pragma mark - Retrieving character attribute information +///============================================================================= +/// @name Retrieving character attribute information +///============================================================================= + +/** + Returns the attributes at first charactor. + */ +@property (nullable, nonatomic, copy, readonly) NSDictionary *yy_attributes; + +/** + Returns the attributes for the character at a given index. + + @discussion Raises an `NSRangeException` if index lies beyond the end of the + receiver's characters. + + @param index The index for which to return attributes. + This value must lie within the bounds of the receiver. + + @return The attributes for the character at index. + */ +- (nullable NSDictionary *)yy_attributesAtIndex:(NSUInteger)index; + +/** + Returns the value for an attribute with a given name of the character at a given index. + + @discussion Raises an `NSRangeException` if index lies beyond the end of the + receiver's characters. + + @param attributeName The name of an attribute. + @param index The index for which to return attributes. + This value must not exceed the bounds of the receiver. + + @return The value for the attribute named `attributeName` of the character at + index `index`, or nil if there is no such attribute. + */ +- (nullable id)yy_attribute:(NSString *)attributeName atIndex:(NSUInteger)index; + + +#pragma mark - Get character attribute as property +///============================================================================= +/// @name Get character attribute as property +///============================================================================= + +/** + The font of the text. (read-only) + + @discussion Default is Helvetica (Neue) 12. + @discussion Get this property returns the first character's attribute. + @since CoreText:3.2 UIKit:6.0 YYText:6.0 + */ +@property (nullable, nonatomic, strong, readonly) UIFont *yy_font; +- (nullable UIFont *)yy_fontAtIndex:(NSUInteger)index; + +/** + A kerning adjustment. (read-only) + + @discussion Default is standard kerning. The kerning attribute indicate how many + points the following character should be shifted from its default offset as + defined by the current character's font in points; a positive kern indicates a + shift farther along and a negative kern indicates a shift closer to the current + character. If this attribute is not present, standard kerning will be used. + If this attribute is set to 0.0, no kerning will be done at all. + @discussion Get this property returns the first character's attribute. + @since CoreText:3.2 UIKit:6.0 YYText:6.0 + */ +@property (nullable, nonatomic, strong, readonly) NSNumber *yy_kern; +- (nullable NSNumber *)yy_kernAtIndex:(NSUInteger)index; + +/** + The foreground color. (read-only) + + @discussion Default is Black. + @discussion Get this property returns the first character's attribute. + @since CoreText:3.2 UIKit:6.0 YYText:6.0 + */ +@property (nullable, nonatomic, strong, readonly) UIColor *yy_color; +- (nullable UIColor *)yy_colorAtIndex:(NSUInteger)index; + +/** + The background color. (read-only) + + @discussion Default is nil (or no background). + @discussion Get this property returns the first character's attribute. + @since UIKit:6.0 + */ +@property (nullable, nonatomic, strong, readonly) UIColor *yy_backgroundColor; +- (nullable UIColor *)yy_backgroundColorAtIndex:(NSUInteger)index; + +/** + The stroke width. (read-only) + + @discussion Default value is 0.0 (no stroke). This attribute, interpreted as + a percentage of font point size, controls the text drawing mode: positive + values effect drawing with stroke only; negative values are for stroke and fill. + A typical value for outlined text is 3.0. + @discussion Get this property returns the first character's attribute. + @since CoreText:3.2 UIKit:6.0 + */ +@property (nullable, nonatomic, strong, readonly) NSNumber *yy_strokeWidth; +- (nullable NSNumber *)yy_strokeWidthAtIndex:(NSUInteger)index; + +/** + The stroke color. (read-only) + + @discussion Default value is nil (same as foreground color). + @discussion Get this property returns the first character's attribute. + @since CoreText:3.2 UIKit:6.0 + */ +@property (nullable, nonatomic, strong, readonly) UIColor *yy_strokeColor; +- (nullable UIColor *)yy_strokeColorAtIndex:(NSUInteger)index; + +/** + The text shadow. (read-only) + + @discussion Default value is nil (no shadow). + @discussion Get this property returns the first character's attribute. + @since UIKit:6.0 YYText:6.0 + */ +@property (nullable, nonatomic, strong, readonly) NSShadow *yy_shadow; +- (nullable NSShadow *)yy_shadowAtIndex:(NSUInteger)index; + +/** + The strikethrough style. (read-only) + + @discussion Default value is NSUnderlineStyleNone (no strikethrough). + @discussion Get this property returns the first character's attribute. + @since UIKit:6.0 + */ +@property (nonatomic, readonly) NSUnderlineStyle yy_strikethroughStyle; +- (NSUnderlineStyle)yy_strikethroughStyleAtIndex:(NSUInteger)index; + +/** + The strikethrough color. (read-only) + + @discussion Default value is nil (same as foreground color). + @discussion Get this property returns the first character's attribute. + @since UIKit:7.0 + */ +@property (nullable, nonatomic, strong, readonly) UIColor *yy_strikethroughColor; +- (nullable UIColor *)yy_strikethroughColorAtIndex:(NSUInteger)index; + +/** + The underline style. (read-only) + + @discussion Default value is NSUnderlineStyleNone (no underline). + @discussion Get this property returns the first character's attribute. + @since CoreText:3.2 UIKit:6.0 + */ +@property (nonatomic, readonly) NSUnderlineStyle yy_underlineStyle; +- (NSUnderlineStyle)yy_underlineStyleAtIndex:(NSUInteger)index; + +/** + The underline color. (read-only) + + @discussion Default value is nil (same as foreground color). + @discussion Get this property returns the first character's attribute. + @since CoreText:3.2 UIKit:7.0 + */ +@property (nullable, nonatomic, strong, readonly) UIColor *yy_underlineColor; +- (nullable UIColor *)yy_underlineColorAtIndex:(NSUInteger)index; + +/** + Ligature formation control. (read-only) + + @discussion Default is int value 1. The ligature attribute determines what kinds + of ligatures should be used when displaying the string. A value of 0 indicates + that only ligatures essential for proper rendering of text should be used, + 1 indicates that standard ligatures should be used, and 2 indicates that all + available ligatures should be used. Which ligatures are standard depends on the + script and possibly the font. + @discussion Get this property returns the first character's attribute. + @since CoreText:3.2 UIKit:6.0 YYText:6.0 + */ +@property (nullable, nonatomic, strong, readonly) NSNumber *yy_ligature; +- (nullable NSNumber *)yy_ligatureAtIndex:(NSUInteger)index; + +/** + The text effect. (read-only) + + @discussion Default is nil (no effect). The only currently supported value + is NSTextEffectLetterpressStyle. + @discussion Get this property returns the first character's attribute. + @since UIKit:7.0 + */ +@property (nullable, nonatomic, strong, readonly) NSString *yy_textEffect; +- (nullable NSString *)yy_textEffectAtIndex:(NSUInteger)index; + +/** + The skew to be applied to glyphs. (read-only) + + @discussion Default is 0 (no skew). + @discussion Get this property returns the first character's attribute. + @since UIKit:7.0 + */ +@property (nullable, nonatomic, strong, readonly) NSNumber *yy_obliqueness; +- (nullable NSNumber *)yy_obliquenessAtIndex:(NSUInteger)index; + +/** + The log of the expansion factor to be applied to glyphs. (read-only) + + @discussion Default is 0 (no expansion). + @discussion Get this property returns the first character's attribute. + @since UIKit:7.0 + */ +@property (nullable, nonatomic, strong, readonly) NSNumber *yy_expansion; +- (nullable NSNumber *)yy_expansionAtIndex:(NSUInteger)index; + +/** + The character's offset from the baseline, in points. (read-only) + + @discussion Default is 0. + @discussion Get this property returns the first character's attribute. + @since UIKit:7.0 + */ +@property (nullable, nonatomic, strong, readonly) NSNumber *yy_baselineOffset; +- (nullable NSNumber *)yy_baselineOffsetAtIndex:(NSUInteger)index; + +/** + Glyph orientation control. (read-only) + + @discussion Default is NO. A value of NO indicates that horizontal glyph forms + are to be used, YES indicates that vertical glyph forms are to be used. + @discussion Get this property returns the first character's attribute. + @since CoreText:4.3 YYText:6.0 + */ +@property (nonatomic, readonly) BOOL yy_verticalGlyphForm; +- (BOOL)yy_verticalGlyphFormAtIndex:(NSUInteger)index; + +/** + Specifies text language. (read-only) + + @discussion Value must be a NSString containing a locale identifier. Default is + unset. When this attribute is set to a valid identifier, it will be used to select + localized glyphs (if supported by the font) and locale-specific line breaking rules. + @discussion Get this property returns the first character's attribute. + @since CoreText:7.0 YYText:7.0 + */ +@property (nullable, nonatomic, strong, readonly) NSString *yy_language; +- (nullable NSString *)yy_languageAtIndex:(NSUInteger)index; + +/** + Specifies a bidirectional override or embedding. (read-only) + + @discussion See alse NSWritingDirection and NSWritingDirectionAttributeName. + @discussion Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:7.0 YYText:6.0 + */ +@property (nullable, nonatomic, strong, readonly) NSArray *yy_writingDirection; +- (nullable NSArray *)yy_writingDirectionAtIndex:(NSUInteger)index; + +/** + An NSParagraphStyle object which is used to specify things like + line alignment, tab rulers, writing direction, etc. (read-only) + + @discussion Default is nil ([NSParagraphStyle defaultParagraphStyle]). + @discussion Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nullable, nonatomic, strong, readonly) NSParagraphStyle *yy_paragraphStyle; +- (nullable NSParagraphStyle *)yy_paragraphStyleAtIndex:(NSUInteger)index; + +#pragma mark - Get paragraph attribute as property +///============================================================================= +/// @name Get paragraph attribute as property +///============================================================================= + +/** + The text alignment (A wrapper for NSParagraphStyle). (read-only) + + @discussion Natural text alignment is realized as left or right alignment + depending on the line sweep direction of the first script contained in the paragraph. + @discussion Default is NSTextAlignmentNatural. + @discussion Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readonly) NSTextAlignment yy_alignment; +- (NSTextAlignment)yy_alignmentAtIndex:(NSUInteger)index; + +/** + The mode that should be used to break lines (A wrapper for NSParagraphStyle). (read-only) + + @discussion This property contains the line break mode to be used laying out the paragraph's text. + @discussion Default is NSLineBreakByWordWrapping. + @discussion Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readonly) NSLineBreakMode yy_lineBreakMode; +- (NSLineBreakMode)yy_lineBreakModeAtIndex:(NSUInteger)index; + +/** + The distance in points between the bottom of one line fragment and the top of the next. + (A wrapper for NSParagraphStyle) (read-only) + + @discussion This value is always nonnegative. This value is included in the line + fragment heights in the layout manager. + @discussion Default is 0. + @discussion Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readonly) CGFloat yy_lineSpacing; +- (CGFloat)yy_lineSpacingAtIndex:(NSUInteger)index; + +/** + The space after the end of the paragraph (A wrapper for NSParagraphStyle). (read-only) + + @discussion This property contains the space (measured in points) added at the + end of the paragraph to separate it from the following paragraph. This value must + be nonnegative. The space between paragraphs is determined by adding the previous + paragraph's paragraphSpacing and the current paragraph's paragraphSpacingBefore. + @discussion Default is 0. + @discussion Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readonly) CGFloat yy_paragraphSpacing; +- (CGFloat)yy_paragraphSpacingAtIndex:(NSUInteger)index; + +/** + The distance between the paragraph's top and the beginning of its text content. + (A wrapper for NSParagraphStyle). (read-only) + + @discussion This property contains the space (measured in points) between the + paragraph's top and the beginning of its text content. + @discussion Default is 0. + @discussion Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readonly) CGFloat yy_paragraphSpacingBefore; +- (CGFloat)yy_paragraphSpacingBeforeAtIndex:(NSUInteger)index; + +/** + The indentation of the first line (A wrapper for NSParagraphStyle). (read-only) + + @discussion This property contains the distance (in points) from the leading margin + of a text container to the beginning of the paragraph's first line. This value + is always nonnegative. + @discussion Default is 0. + @discussion Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readonly) CGFloat yy_firstLineHeadIndent; +- (CGFloat)yy_firstLineHeadIndentAtIndex:(NSUInteger)index; + +/** + The indentation of the receiver's lines other than the first. (A wrapper for NSParagraphStyle). (read-only) + + @discussion This property contains the distance (in points) from the leading margin + of a text container to the beginning of lines other than the first. This value is + always nonnegative. + @discussion Default is 0. + @discussion Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readonly) CGFloat yy_headIndent; +- (CGFloat)yy_headIndentAtIndex:(NSUInteger)index; + +/** + The trailing indentation (A wrapper for NSParagraphStyle). (read-only) + + @discussion If positive, this value is the distance from the leading margin + (for example, the left margin in left-to-right text). If 0 or negative, it's the + distance from the trailing margin. + @discussion Default is 0. + @discussion Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readonly) CGFloat yy_tailIndent; +- (CGFloat)yy_tailIndentAtIndex:(NSUInteger)index; + +/** + The receiver's minimum height (A wrapper for NSParagraphStyle). (read-only) + + @discussion This property contains the minimum height in points that any line in + the receiver will occupy, regardless of the font size or size of any attached graphic. + This value must be nonnegative. + @discussion Default is 0. + @discussion Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readonly) CGFloat yy_minimumLineHeight; +- (CGFloat)yy_minimumLineHeightAtIndex:(NSUInteger)index; + +/** + The receiver's maximum line height (A wrapper for NSParagraphStyle). (read-only) + + @discussion This property contains the maximum height in points that any line in + the receiver will occupy, regardless of the font size or size of any attached graphic. + This value is always nonnegative. Glyphs and graphics exceeding this height will + overlap neighboring lines; however, a maximum height of 0 implies no line height limit. + Although this limit applies to the line itself, line spacing adds extra space between adjacent lines. + @discussion Default is 0 (no limit). + @discussion Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readonly) CGFloat yy_maximumLineHeight; +- (CGFloat)yy_maximumLineHeightAtIndex:(NSUInteger)index; + +/** + The line height multiple (A wrapper for NSParagraphStyle). (read-only) + + @discussion This property contains the line break mode to be used laying out the paragraph's text. + @discussion Default is 0 (no multiple). + @discussion Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readonly) CGFloat yy_lineHeightMultiple; +- (CGFloat)yy_lineHeightMultipleAtIndex:(NSUInteger)index; + +/** + The base writing direction (A wrapper for NSParagraphStyle). (read-only) + + @discussion If you specify NSWritingDirectionNaturalDirection, the receiver resolves + the writing direction to either NSWritingDirectionLeftToRight or NSWritingDirectionRightToLeft, + depending on the direction for the user's `language` preference setting. + @discussion Default is NSWritingDirectionNatural. + @discussion Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readonly) NSWritingDirection yy_baseWritingDirection; +- (NSWritingDirection)yy_baseWritingDirectionAtIndex:(NSUInteger)index; + +/** + The paragraph's threshold for hyphenation. (A wrapper for NSParagraphStyle). (read-only) + + @discussion Valid values lie between 0.0 and 1.0 inclusive. Hyphenation is attempted + when the ratio of the text width (as broken without hyphenation) to the width of the + line fragment is less than the hyphenation factor. When the paragraph's hyphenation + factor is 0.0, the layout manager's hyphenation factor is used instead. When both + are 0.0, hyphenation is disabled. + @discussion Default is 0. + @discussion Get this property returns the first character's attribute. + @since UIKit:6.0 + */ +@property (nonatomic, readonly) float yy_hyphenationFactor; +- (float)yy_hyphenationFactorAtIndex:(NSUInteger)index; + +/** + The document-wide default tab interval (A wrapper for NSParagraphStyle). (read-only) + + @discussion This property represents the default tab interval in points. Tabs after the + last specified in tabStops are placed at integer multiples of this distance (if positive). + @discussion Default is 0. + @discussion Get this property returns the first character's attribute. + @since CoreText:7.0 UIKit:7.0 YYText:7.0 + */ +@property (nonatomic, readonly) CGFloat yy_defaultTabInterval; +- (CGFloat)yy_defaultTabIntervalAtIndex:(NSUInteger)index; + +/** + An array of NSTextTab objects representing the receiver's tab stops. + (A wrapper for NSParagraphStyle). (read-only) + + @discussion The NSTextTab objects, sorted by location, define the tab stops for + the paragraph style. + @discussion Default is 12 TabStops with 28.0 tab interval. + @discussion Get this property returns the first character's attribute. + @since CoreText:7.0 UIKit:7.0 YYText:7.0 + */ +@property (nullable, nonatomic, copy, readonly) NSArray *yy_tabStops; +- (nullable NSArray *)yy_tabStopsAtIndex:(NSUInteger)index; + +#pragma mark - Get YYText attribute as property +///============================================================================= +/// @name Get YYText attribute as property +///============================================================================= + +/** + The text shadow. (read-only) + + @discussion Default value is nil (no shadow). + @discussion Get this property returns the first character's attribute. + @since YYText:6.0 + */ +@property (nullable, nonatomic, strong, readonly) YYTextShadow *yy_textShadow; +- (nullable YYTextShadow *)yy_textShadowAtIndex:(NSUInteger)index; + +/** + The text inner shadow. (read-only) + + @discussion Default value is nil (no shadow). + @discussion Get this property returns the first character's attribute. + @since YYText:6.0 + */ +@property (nullable, nonatomic, strong, readonly) YYTextShadow *yy_textInnerShadow; +- (nullable YYTextShadow *)yy_textInnerShadowAtIndex:(NSUInteger)index; + +/** + The text underline. (read-only) + + @discussion Default value is nil (no underline). + @discussion Get this property returns the first character's attribute. + @since YYText:6.0 + */ +@property (nullable, nonatomic, strong, readonly) YYTextDecoration *yy_textUnderline; +- (nullable YYTextDecoration *)yy_textUnderlineAtIndex:(NSUInteger)index; + +/** + The text strikethrough. (read-only) + + @discussion Default value is nil (no strikethrough). + @discussion Get this property returns the first character's attribute. + @since YYText:6.0 + */ +@property (nullable, nonatomic, strong, readonly) YYTextDecoration *yy_textStrikethrough; +- (nullable YYTextDecoration *)yy_textStrikethroughAtIndex:(NSUInteger)index; + +/** + The text border. (read-only) + + @discussion Default value is nil (no border). + @discussion Get this property returns the first character's attribute. + @since YYText:6.0 + */ +@property (nullable, nonatomic, strong, readonly) YYTextBorder *yy_textBorder; +- (nullable YYTextBorder *)yy_textBorderAtIndex:(NSUInteger)index; + +/** + The text background border. (read-only) + + @discussion Default value is nil (no background border). + @discussion Get this property returns the first character's attribute. + @since YYText:6.0 + */ +@property (nullable, nonatomic, strong, readonly) YYTextBorder *yy_textBackgroundBorder; +- (nullable YYTextBorder *)yy_textBackgroundBorderAtIndex:(NSUInteger)index; + +/** + The glyph transform. (read-only) + + @discussion Default value is CGAffineTransformIdentity (no transform). + @discussion Get this property returns the first character's attribute. + @since YYText:6.0 + */ +@property (nonatomic, readonly) CGAffineTransform yy_textGlyphTransform; +- (CGAffineTransform)yy_textGlyphTransformAtIndex:(NSUInteger)index; + + +#pragma mark - Query for YYText +///============================================================================= +/// @name Query for YYText +///============================================================================= + +/** + Returns the plain text from a range. + If there's `YYTextBackedStringAttributeName` attribute, the backed string will + replace the attributed string range. + + @param range A range in receiver. + @return The plain text. + */ +- (nullable NSString *)yy_plainTextForRange:(NSRange)range; + + +#pragma mark - Create attachment string for YYText +///============================================================================= +/// @name Create attachment string for YYText +///============================================================================= + +/** + Creates and returns an attachment. + + @param content The attachment (UIImage/UIView/CALayer). + @param contentMode The attachment's content mode. + @param width The attachment's container width in layout. + @param ascent The attachment's container ascent in layout. + @param descent The attachment's container descent in layout. + + @return An attributed string, or nil if an error occurs. + @since YYText:6.0 + */ ++ (NSMutableAttributedString *)yy_attachmentStringWithContent:(nullable id)content + contentMode:(UIViewContentMode)contentMode + width:(CGFloat)width + ascent:(CGFloat)ascent + descent:(CGFloat)descent; + +/** + Creates and returns an attachment. + + + Example: ContentMode:bottom Alignment:Top. + + The text The attachment holder + ↓ ↓ + ─────────┌──────────────────────┐─────── + / \ │ │ / ___| + / _ \ │ │| | + / ___ \ │ │| |___ ←── The text line + /_/ \_\│ ██████████████ │ \____| + ─────────│ ██████████████ │─────── + │ ██████████████ │ + │ ██████████████ ←───────────────── The attachment content + │ ██████████████ │ + └──────────────────────┘ + + @param content The attachment (UIImage/UIView/CALayer). + @param contentMode The attachment's content mode in attachment holder + @param attachmentSize The attachment holder's size in text layout. + @param font The attachment will align to this font. + @param alignment The attachment holder's alignment to text line. + + @return An attributed string, or nil if an error occurs. + @since YYText:6.0 + */ ++ (NSMutableAttributedString *)yy_attachmentStringWithContent:(nullable id)content + contentMode:(UIViewContentMode)contentMode + attachmentSize:(CGSize)attachmentSize + alignToFont:(UIFont *)font + alignment:(YYTextVerticalAlignment)alignment; + +/** + Creates and returns an attahment from a fourquare image as if it was an emoji. + + @param image A fourquare image. + @param fontSize The font size. + + @return An attributed string, or nil if an error occurs. + @since YYText:6.0 + */ ++ (nullable NSMutableAttributedString *)yy_attachmentStringWithEmojiImage:(UIImage *)image + fontSize:(CGFloat)fontSize; + +#pragma mark - Utility +///============================================================================= +/// @name Utility +///============================================================================= + +/** + Returns NSMakeRange(0, self.length). + */ +- (NSRange)yy_rangeOfAll; + +/** + If YES, it share the same attribute in entire text range. + */ +- (BOOL)yy_isSharedAttributesInAllRange; + +/** + If YES, it can be drawn with the [drawWithRect:options:context:] method or displayed with UIKit. + If NO, it should be drawn with CoreText or YYText. + + @discussion If the method returns NO, it means that there's at least one attribute + which is not supported by UIKit (such as CTParagraphStyleRef). If display this string + in UIKit, it may lose some attribute, or even crash the app. + */ +- (BOOL)yy_canDrawWithUIKit; + +@end + + + + +/** + Set pre-defined attributes to attributed string. + All properties defined in UIKit, CoreText and YYText are included. + */ +@interface NSMutableAttributedString (YYText) + +#pragma mark - Set character attribute +///============================================================================= +/// @name Set character attribute +///============================================================================= + +/** + Sets the attributes to the entire text string. + + @discussion The old attributes will be removed. + + @param attributes A dictionary containing the attributes to set, or nil to remove all attributes. + */ +- (void)yy_setAttributes:(nullable NSDictionary *)attributes; +- (void)setYy_attributes:(nullable NSDictionary *)attributes; + +/** + Sets an attribute with the given name and value to the entire text string. + + @param name A string specifying the attribute name. + @param value The attribute value associated with name. Pass `nil` or `NSNull` to + remove the attribute. + */ +- (void)yy_setAttribute:(NSString *)name value:(nullable id)value; + +/** + Sets an attribute with the given name and value to the characters in the specified range. + + @param name A string specifying the attribute name. + @param value The attribute value associated with name. Pass `nil` or `NSNull` to + remove the attribute. + @param range The range of characters to which the specified attribute/value pair applies. + */ +- (void)yy_setAttribute:(NSString *)name value:(nullable id)value range:(NSRange)range; + +/** + Removes all attributes in the specified range. + + @param range The range of characters. + */ +- (void)yy_removeAttributesInRange:(NSRange)range; + + +#pragma mark - Set character attribute as property +///============================================================================= +/// @name Set character attribute as property +///============================================================================= + +/** + The font of the text. + + @discussion Default is Helvetica (Neue) 12. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:3.2 UIKit:6.0 YYText:6.0 + */ +@property (nullable, nonatomic, strong, readwrite) UIFont *yy_font; +- (void)yy_setFont:(nullable UIFont *)font range:(NSRange)range; + +/** + A kerning adjustment. + + @discussion Default is standard kerning. The kerning attribute indicate how many + points the following character should be shifted from its default offset as + defined by the current character's font in points; a positive kern indicates a + shift farther along and a negative kern indicates a shift closer to the current + character. If this attribute is not present, standard kerning will be used. + If this attribute is set to 0.0, no kerning will be done at all. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:3.2 UIKit:6.0 YYText:6.0 + */ +@property (nullable, nonatomic, strong, readwrite) NSNumber *yy_kern; +- (void)yy_setKern:(nullable NSNumber *)kern range:(NSRange)range; + +/** + The foreground color. + + @discussion Default is Black. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:3.2 UIKit:6.0 YYText:6.0 + */ +@property (nullable, nonatomic, strong, readwrite) UIColor *yy_color; +- (void)yy_setColor:(nullable UIColor *)color range:(NSRange)range; + +/** + The background color. + + @discussion Default is nil (or no background). + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since UIKit:6.0 + */ +@property (nullable, nonatomic, strong, readwrite) UIColor *yy_backgroundColor; +- (void)yy_setBackgroundColor:(nullable UIColor *)backgroundColor range:(NSRange)range; + +/** + The stroke width. + + @discussion Default value is 0.0 (no stroke). This attribute, interpreted as + a percentage of font point size, controls the text drawing mode: positive + values effect drawing with stroke only; negative values are for stroke and fill. + A typical value for outlined text is 3.0. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:3.2 UIKit:6.0 YYText:6.0 + */ +@property (nullable, nonatomic, strong, readwrite) NSNumber *yy_strokeWidth; +- (void)yy_setStrokeWidth:(nullable NSNumber *)strokeWidth range:(NSRange)range; + +/** + The stroke color. + + @discussion Default value is nil (same as foreground color). + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:3.2 UIKit:6.0 YYText:6.0 + */ +@property (nullable, nonatomic, strong, readwrite) UIColor *yy_strokeColor; +- (void)yy_setStrokeColor:(nullable UIColor *)strokeColor range:(NSRange)range; + +/** + The text shadow. + + @discussion Default value is nil (no shadow). + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since UIKit:6.0 YYText:6.0 + */ +@property (nullable, nonatomic, strong, readwrite) NSShadow *yy_shadow; +- (void)yy_setShadow:(nullable NSShadow *)shadow range:(NSRange)range; + +/** + The strikethrough style. + + @discussion Default value is NSUnderlineStyleNone (no strikethrough). + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since UIKit:6.0 + */ +@property (nonatomic, readwrite) NSUnderlineStyle yy_strikethroughStyle; +- (void)yy_setStrikethroughStyle:(NSUnderlineStyle)strikethroughStyle range:(NSRange)range; + +/** + The strikethrough color. + + @discussion Default value is nil (same as foreground color). + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since UIKit:7.0 + */ +@property (nullable, nonatomic, strong, readwrite) UIColor *yy_strikethroughColor; +- (void)yy_setStrikethroughColor:(nullable UIColor *)strikethroughColor range:(NSRange)range NS_AVAILABLE_IOS(7_0); + +/** + The underline style. + + @discussion Default value is NSUnderlineStyleNone (no underline). + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:3.2 UIKit:6.0 + */ +@property (nonatomic, readwrite) NSUnderlineStyle yy_underlineStyle; +- (void)yy_setUnderlineStyle:(NSUnderlineStyle)underlineStyle range:(NSRange)range; + +/** + The underline color. + + @discussion Default value is nil (same as foreground color). + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:3.2 UIKit:7.0 + */ +@property (nullable, nonatomic, strong, readwrite) UIColor *yy_underlineColor; +- (void)yy_setUnderlineColor:(nullable UIColor *)underlineColor range:(NSRange)range; + +/** + Ligature formation control. + + @discussion Default is int value 1. The ligature attribute determines what kinds + of ligatures should be used when displaying the string. A value of 0 indicates + that only ligatures essential for proper rendering of text should be used, + 1 indicates that standard ligatures should be used, and 2 indicates that all + available ligatures should be used. Which ligatures are standard depends on the + script and possibly the font. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:3.2 UIKit:6.0 YYText:6.0 + */ +@property (nullable, nonatomic, strong, readwrite) NSNumber *yy_ligature; +- (void)yy_setLigature:(nullable NSNumber *)ligature range:(NSRange)range; + +/** + The text effect. + + @discussion Default is nil (no effect). The only currently supported value + is NSTextEffectLetterpressStyle. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since UIKit:7.0 + */ +@property (nullable, nonatomic, strong, readwrite) NSString *yy_textEffect; +- (void)yy_setTextEffect:(nullable NSString *)textEffect range:(NSRange)range NS_AVAILABLE_IOS(7_0); + +/** + The skew to be applied to glyphs. + + @discussion Default is 0 (no skew). + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since UIKit:7.0 + */ +@property (nullable, nonatomic, strong, readwrite) NSNumber *yy_obliqueness; +- (void)yy_setObliqueness:(nullable NSNumber *)obliqueness range:(NSRange)range NS_AVAILABLE_IOS(7_0); + +/** + The log of the expansion factor to be applied to glyphs. + + @discussion Default is 0 (no expansion). + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since UIKit:7.0 + */ +@property (nullable, nonatomic, strong, readwrite) NSNumber *yy_expansion; +- (void)yy_setExpansion:(nullable NSNumber *)expansion range:(NSRange)range NS_AVAILABLE_IOS(7_0); + +/** + The character's offset from the baseline, in points. + + @discussion Default is 0. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since UIKit:7.0 + */ +@property (nullable, nonatomic, strong, readwrite) NSNumber *yy_baselineOffset; +- (void)yy_setBaselineOffset:(nullable NSNumber *)baselineOffset range:(NSRange)range NS_AVAILABLE_IOS(7_0); + +/** + Glyph orientation control. + + @discussion Default is NO. A value of NO indicates that horizontal glyph forms + are to be used, YES indicates that vertical glyph forms are to be used. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:4.3 YYText:6.0 + */ +@property (nonatomic, readwrite) BOOL yy_verticalGlyphForm; +- (void)yy_setVerticalGlyphForm:(BOOL)verticalGlyphForm range:(NSRange)range; + +/** + Specifies text language. + + @discussion Value must be a NSString containing a locale identifier. Default is + unset. When this attribute is set to a valid identifier, it will be used to select + localized glyphs (if supported by the font) and locale-specific line breaking rules. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:7.0 YYText:7.0 + */ +@property (nullable, nonatomic, strong, readwrite) NSString *yy_language; +- (void)yy_setLanguage:(nullable NSString *)language range:(NSRange)range NS_AVAILABLE_IOS(7_0); + +/** + Specifies a bidirectional override or embedding. + + @discussion See alse NSWritingDirection and NSWritingDirectionAttributeName. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:7.0 YYText:6.0 + */ +@property (nullable, nonatomic, strong, readwrite) NSArray *yy_writingDirection; +- (void)yy_setWritingDirection:(nullable NSArray *)writingDirection range:(NSRange)range; + +/** + An NSParagraphStyle object which is used to specify things like + line alignment, tab rulers, writing direction, etc. + + @discussion Default is nil ([NSParagraphStyle defaultParagraphStyle]). + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nullable, nonatomic, strong, readwrite) NSParagraphStyle *yy_paragraphStyle; +- (void)yy_setParagraphStyle:(nullable NSParagraphStyle *)paragraphStyle range:(NSRange)range; + + +#pragma mark - Set paragraph attribute as property +///============================================================================= +/// @name Set paragraph attribute as property +///============================================================================= + +/** + The text alignment (A wrapper for NSParagraphStyle). + + @discussion Natural text alignment is realized as left or right alignment + depending on the line sweep direction of the first script contained in the paragraph. + @discussion Default is NSTextAlignmentNatural. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readwrite) NSTextAlignment yy_alignment; +- (void)yy_setAlignment:(NSTextAlignment)alignment range:(NSRange)range; + +/** + The mode that should be used to break lines (A wrapper for NSParagraphStyle). + + @discussion This property contains the line break mode to be used laying out the paragraph's text. + @discussion Default is NSLineBreakByWordWrapping. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readwrite) NSLineBreakMode yy_lineBreakMode; +- (void)yy_setLineBreakMode:(NSLineBreakMode)lineBreakMode range:(NSRange)range; + +/** + The distance in points between the bottom of one line fragment and the top of the next. + (A wrapper for NSParagraphStyle) + + @discussion This value is always nonnegative. This value is included in the line + fragment heights in the layout manager. + @discussion Default is 0. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readwrite) CGFloat yy_lineSpacing; +- (void)yy_setLineSpacing:(CGFloat)lineSpacing range:(NSRange)range; + +/** + The space after the end of the paragraph (A wrapper for NSParagraphStyle). + + @discussion This property contains the space (measured in points) added at the + end of the paragraph to separate it from the following paragraph. This value must + be nonnegative. The space between paragraphs is determined by adding the previous + paragraph's paragraphSpacing and the current paragraph's paragraphSpacingBefore. + @discussion Default is 0. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readwrite) CGFloat yy_paragraphSpacing; +- (void)yy_setParagraphSpacing:(CGFloat)paragraphSpacing range:(NSRange)range; + +/** + The distance between the paragraph's top and the beginning of its text content. + (A wrapper for NSParagraphStyle). + + @discussion This property contains the space (measured in points) between the + paragraph's top and the beginning of its text content. + @discussion Default is 0. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readwrite) CGFloat yy_paragraphSpacingBefore; +- (void)yy_setParagraphSpacingBefore:(CGFloat)paragraphSpacingBefore range:(NSRange)range; + +/** + The indentation of the first line (A wrapper for NSParagraphStyle). + + @discussion This property contains the distance (in points) from the leading margin + of a text container to the beginning of the paragraph's first line. This value + is always nonnegative. + @discussion Default is 0. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readwrite) CGFloat yy_firstLineHeadIndent; +- (void)yy_setFirstLineHeadIndent:(CGFloat)firstLineHeadIndent range:(NSRange)range; + +/** + The indentation of the receiver's lines other than the first. (A wrapper for NSParagraphStyle). + + @discussion This property contains the distance (in points) from the leading margin + of a text container to the beginning of lines other than the first. This value is + always nonnegative. + @discussion Default is 0. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readwrite) CGFloat yy_headIndent; +- (void)yy_setHeadIndent:(CGFloat)headIndent range:(NSRange)range; + +/** + The trailing indentation (A wrapper for NSParagraphStyle). + + @discussion If positive, this value is the distance from the leading margin + (for example, the left margin in left-to-right text). If 0 or negative, it's the + distance from the trailing margin. + @discussion Default is 0. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readwrite) CGFloat yy_tailIndent; +- (void)yy_setTailIndent:(CGFloat)tailIndent range:(NSRange)range; + +/** + The receiver's minimum height (A wrapper for NSParagraphStyle). + + @discussion This property contains the minimum height in points that any line in + the receiver will occupy, regardless of the font size or size of any attached graphic. + This value must be nonnegative. + @discussion Default is 0. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readwrite) CGFloat yy_minimumLineHeight; +- (void)yy_setMinimumLineHeight:(CGFloat)minimumLineHeight range:(NSRange)range; + +/** + The receiver's maximum line height (A wrapper for NSParagraphStyle). + + @discussion This property contains the maximum height in points that any line in + the receiver will occupy, regardless of the font size or size of any attached graphic. + This value is always nonnegative. Glyphs and graphics exceeding this height will + overlap neighboring lines; however, a maximum height of 0 implies no line height limit. + Although this limit applies to the line itself, line spacing adds extra space between adjacent lines. + @discussion Default is 0 (no limit). + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readwrite) CGFloat yy_maximumLineHeight; +- (void)yy_setMaximumLineHeight:(CGFloat)maximumLineHeight range:(NSRange)range; + +/** + The line height multiple (A wrapper for NSParagraphStyle). + + @discussion This property contains the line break mode to be used laying out the paragraph's text. + @discussion Default is 0 (no multiple). + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readwrite) CGFloat yy_lineHeightMultiple; +- (void)yy_setLineHeightMultiple:(CGFloat)lineHeightMultiple range:(NSRange)range; + +/** + The base writing direction (A wrapper for NSParagraphStyle). + + @discussion If you specify NSWritingDirectionNaturalDirection, the receiver resolves + the writing direction to either NSWritingDirectionLeftToRight or NSWritingDirectionRightToLeft, + depending on the direction for the user's `language` preference setting. + @discussion Default is NSWritingDirectionNatural. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:6.0 UIKit:6.0 YYText:6.0 + */ +@property (nonatomic, readwrite) NSWritingDirection yy_baseWritingDirection; +- (void)yy_setBaseWritingDirection:(NSWritingDirection)baseWritingDirection range:(NSRange)range; + +/** + The paragraph's threshold for hyphenation. (A wrapper for NSParagraphStyle). + + @discussion Valid values lie between 0.0 and 1.0 inclusive. Hyphenation is attempted + when the ratio of the text width (as broken without hyphenation) to the width of the + line fragment is less than the hyphenation factor. When the paragraph's hyphenation + factor is 0.0, the layout manager's hyphenation factor is used instead. When both + are 0.0, hyphenation is disabled. + @discussion Default is 0. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since UIKit:6.0 + */ +@property (nonatomic, readwrite) float yy_hyphenationFactor; +- (void)yy_setHyphenationFactor:(float)hyphenationFactor range:(NSRange)range; + +/** + The document-wide default tab interval (A wrapper for NSParagraphStyle). + + @discussion This property represents the default tab interval in points. Tabs after the + last specified in tabStops are placed at integer multiples of this distance (if positive). + @discussion Default is 0. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:7.0 UIKit:7.0 YYText:7.0 + */ +@property (nonatomic, readwrite) CGFloat yy_defaultTabInterval; +- (void)yy_setDefaultTabInterval:(CGFloat)defaultTabInterval range:(NSRange)range NS_AVAILABLE_IOS(7_0); + +/** + An array of NSTextTab objects representing the receiver's tab stops. + (A wrapper for NSParagraphStyle). + + @discussion The NSTextTab objects, sorted by location, define the tab stops for + the paragraph style. + @discussion Default is 12 TabStops with 28.0 tab interval. + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since CoreText:7.0 UIKit:7.0 YYText:7.0 + */ +@property (nullable, nonatomic, copy, readwrite) NSArray *yy_tabStops; +- (void)yy_setTabStops:(nullable NSArray *)tabStops range:(NSRange)range NS_AVAILABLE_IOS(7_0); + +#pragma mark - Set YYText attribute as property +///============================================================================= +/// @name Set YYText attribute as property +///============================================================================= + +/** + The text shadow. + + @discussion Default value is nil (no shadow). + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since YYText:6.0 + */ +@property (nullable, nonatomic, strong, readwrite) YYTextShadow *yy_textShadow; +- (void)yy_setTextShadow:(nullable YYTextShadow *)textShadow range:(NSRange)range; + +/** + The text inner shadow. + + @discussion Default value is nil (no shadow). + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since YYText:6.0 + */ +@property (nullable, nonatomic, strong, readwrite) YYTextShadow *yy_textInnerShadow; +- (void)yy_setTextInnerShadow:(nullable YYTextShadow *)textInnerShadow range:(NSRange)range; + +/** + The text underline. + + @discussion Default value is nil (no underline). + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since YYText:6.0 + */ +@property (nullable, nonatomic, strong, readwrite) YYTextDecoration *yy_textUnderline; +- (void)yy_setTextUnderline:(nullable YYTextDecoration *)textUnderline range:(NSRange)range; + +/** + The text strikethrough. + + @discussion Default value is nil (no strikethrough). + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since YYText:6.0 + */ +@property (nullable, nonatomic, strong, readwrite) YYTextDecoration *yy_textStrikethrough; +- (void)yy_setTextStrikethrough:(nullable YYTextDecoration *)textStrikethrough range:(NSRange)range; + +/** + The text border. + + @discussion Default value is nil (no border). + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since YYText:6.0 + */ +@property (nullable, nonatomic, strong, readwrite) YYTextBorder *yy_textBorder; +- (void)yy_setTextBorder:(nullable YYTextBorder *)textBorder range:(NSRange)range; + +/** + The text background border. + + @discussion Default value is nil (no background border). + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since YYText:6.0 + */ +@property (nullable, nonatomic, strong, readwrite) YYTextBorder *yy_textBackgroundBorder; +- (void)yy_setTextBackgroundBorder:(nullable YYTextBorder *)textBackgroundBorder range:(NSRange)range; + +/** + The glyph transform. + + @discussion Default value is CGAffineTransformIdentity (no transform). + @discussion Set this property applies to the entire text string. + Get this property returns the first character's attribute. + @since YYText:6.0 + */ +@property (nonatomic, readwrite) CGAffineTransform yy_textGlyphTransform; +- (void)yy_setTextGlyphTransform:(CGAffineTransform)textGlyphTransform range:(NSRange)range; + + +#pragma mark - Set discontinuous attribute for range +///============================================================================= +/// @name Set discontinuous attribute for range +///============================================================================= + +- (void)yy_setSuperscript:(nullable NSNumber *)superscript range:(NSRange)range; +- (void)yy_setGlyphInfo:(nullable CTGlyphInfoRef)glyphInfo range:(NSRange)range; +- (void)yy_setCharacterShape:(nullable NSNumber *)characterShape range:(NSRange)range; +- (void)yy_setRunDelegate:(nullable CTRunDelegateRef)runDelegate range:(NSRange)range; +- (void)yy_setBaselineClass:(nullable CFStringRef)baselineClass range:(NSRange)range; +- (void)yy_setBaselineInfo:(nullable CFDictionaryRef)baselineInfo range:(NSRange)range; +- (void)yy_setBaselineReferenceInfo:(nullable CFDictionaryRef)referenceInfo range:(NSRange)range; +- (void)yy_setRubyAnnotation:(nullable CTRubyAnnotationRef)ruby range:(NSRange)range NS_AVAILABLE_IOS(8_0); +- (void)yy_setAttachment:(nullable NSTextAttachment *)attachment range:(NSRange)range NS_AVAILABLE_IOS(7_0); +- (void)yy_setLink:(nullable id)link range:(NSRange)range NS_AVAILABLE_IOS(7_0); +- (void)yy_setTextBackedString:(nullable YYTextBackedString *)textBackedString range:(NSRange)range; +- (void)yy_setTextBinding:(nullable YYTextBinding *)textBinding range:(NSRange)range; +- (void)yy_setTextAttachment:(nullable YYTextAttachment *)textAttachment range:(NSRange)range; +- (void)yy_setTextHighlight:(nullable YYTextHighlight *)textHighlight range:(NSRange)range; +- (void)yy_setTextBlockBorder:(nullable YYTextBorder *)textBlockBorder range:(NSRange)range; +- (void)yy_setTextRubyAnnotation:(nullable YYTextRubyAnnotation *)ruby range:(NSRange)range NS_AVAILABLE_IOS(8_0); + + +#pragma mark - Convenience methods for text highlight +///============================================================================= +/// @name Convenience methods for text highlight +///============================================================================= + +/** + Convenience method to set text highlight + + @param range text range + @param color text color (pass nil to ignore) + @param backgroundColor text background color when highlight + @param userInfo user information dictionary (pass nil to ignore) + @param tapAction tap action when user tap the highlight (pass nil to ignore) + @param longPressAction long press action when user long press the highlight (pass nil to ignore) + */ +- (void)yy_setTextHighlightRange:(NSRange)range + color:(nullable UIColor *)color + backgroundColor:(nullable UIColor *)backgroundColor + userInfo:(nullable NSDictionary *)userInfo + tapAction:(nullable YYTextAction)tapAction + longPressAction:(nullable YYTextAction)longPressAction; + +/** + Convenience method to set text highlight + + @param range text range + @param color text color (pass nil to ignore) + @param backgroundColor text background color when highlight + @param tapAction tap action when user tap the highlight (pass nil to ignore) + */ +- (void)yy_setTextHighlightRange:(NSRange)range + color:(nullable UIColor *)color + backgroundColor:(nullable UIColor *)backgroundColor + tapAction:(nullable YYTextAction)tapAction; + +/** + Convenience method to set text highlight + + @param range text range + @param color text color (pass nil to ignore) + @param backgroundColor text background color when highlight + @param userInfo tap action when user tap the highlight (pass nil to ignore) + */ +- (void)yy_setTextHighlightRange:(NSRange)range + color:(nullable UIColor *)color + backgroundColor:(nullable UIColor *)backgroundColor + userInfo:(nullable NSDictionary *)userInfo; + +#pragma mark - Utilities +///============================================================================= +/// @name Utilities +///============================================================================= + +/** + Inserts into the receiver the characters of a given string at a given location. + The new string inherit the attributes of the first replaced character from location. + + @param string The string to insert into the receiver, must not be nil. + @param location The location at which string is inserted. The location must not + exceed the bounds of the receiver. + @throw Raises an NSRangeException if the location out of bounds. + */ +- (void)yy_insertString:(NSString *)string atIndex:(NSUInteger)location; + +/** + Adds to the end of the receiver the characters of a given string. + The new string inherit the attributes of the receiver's tail. + + @param string The string to append to the receiver, must not be nil. + */ +- (void)yy_appendString:(NSString *)string; + +/** + Set foreground color with [UIColor clearColor] in joined-emoji range. + Emoji drawing will not be affected by the foreground color. + + @discussion In iOS 8.3, Apple releases some new diversified emojis. + There's some single emoji which can be assembled to a new 'joined-emoji'. + The joiner is unicode character 'ZERO WIDTH JOINER' (U+200D). + For example: 👨👩👧👧 -> 👨‍👩‍👧‍👧. + + When there are more than 5 'joined-emoji' in a same CTLine, CoreText may render some + extra glyphs above the emoji. It's a bug in CoreText, try this method to avoid. + This bug is fixed in iOS 9. + */ +- (void)yy_setClearColorToJoinedEmoji; + +/** + Removes all discontinuous attributes in a specified range. + See `allDiscontinuousAttributeKeys`. + + @param range A text range. + */ +- (void)yy_removeDiscontinuousAttributesInRange:(NSRange)range; + +/** + Returns all discontinuous attribute keys, such as RunDelegate/Attachment/Ruby. + + @discussion These attributes can only set to a specified range of text, and + should not extend to other range when editing text. + */ ++ (NSArray *)yy_allDiscontinuousAttributeKeys; + +@end + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/NSParagraphStyle+YYText.h b/YYText/include/NSParagraphStyle+YYText.h new file mode 100644 index 00000000..49b8acd7 --- /dev/null +++ b/YYText/include/NSParagraphStyle+YYText.h @@ -0,0 +1,37 @@ +// +// NSParagraphStyle+YYText.h +// YYText +// +// Created by ibireme on 14/10/7. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Provides extensions for `NSParagraphStyle` to work with CoreText. + */ +@interface NSParagraphStyle (YYText) + +/** + Creates a new NSParagraphStyle object from the CoreText Style. + + @param CTStyle CoreText Paragraph Style. + + @return a new NSParagraphStyle + */ ++ (nullable NSParagraphStyle *)yy_styleWithCTStyle:(CTParagraphStyleRef)CTStyle; + +/** + Creates and returns a CoreText Paragraph Style. (need call CFRelease() after used) + */ +- (nullable CTParagraphStyleRef)yy_CTStyle CF_RETURNS_RETAINED; + +@end + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/UIPasteboard+YYText.h b/YYText/include/UIPasteboard+YYText.h new file mode 100644 index 00000000..d516a3d4 --- /dev/null +++ b/YYText/include/UIPasteboard+YYText.h @@ -0,0 +1,41 @@ +// +// UIPasteboard+YYText.h +// YYText +// +// Created by ibireme on 15/4/2. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Extend UIPasteboard to support image and attributed string. + */ +@interface UIPasteboard (YYText) + +@property (nullable, nonatomic, copy) NSData *yy_PNGData; ///< PNG file data +@property (nullable, nonatomic, copy) NSData *yy_JPEGData; ///< JPEG file data +@property (nullable, nonatomic, copy) NSData *yy_GIFData; ///< GIF file data +@property (nullable, nonatomic, copy) NSData *yy_WEBPData; ///< WebP file data +@property (nullable, nonatomic, copy) NSData *yy_ImageData; ///< image file data + +/// Attributed string, +/// Set this attributed will also set the string property which is copy from the attributed string. +/// If the attributed string contains one or more image, it will also set the `images` property. +@property (nullable, nonatomic, copy) NSAttributedString *yy_AttributedString; + +@end + + +/// The name identifying the attributed string in pasteboard. +UIKIT_EXTERN NSString *const YYTextPasteboardTypeAttributedString; + +/// The UTI Type identifying WebP data in pasteboard. +UIKIT_EXTERN NSString *const YYTextUTTypeWEBP; + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/UIView+YYText.h b/YYText/include/UIView+YYText.h new file mode 100644 index 00000000..9472834a --- /dev/null +++ b/YYText/include/UIView+YYText.h @@ -0,0 +1,72 @@ +// +// UIView+YYText.h +// YYText +// +// Created by ibireme on 13/4/3. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Provides extensions for `UIView`. + */ +@interface UIView (YYText) + +/** + Returns the view's view controller (may be nil). + */ +@property (nullable, nonatomic, readonly) UIViewController *yy_viewController; + +/** + Returns the visible alpha on screen, taking into account superview and window. + */ +@property (nonatomic, readonly) CGFloat yy_visibleAlpha; + +/** + Converts a point from the receiver's coordinate system to that of the specified view or window. + + @param point A point specified in the local coordinate system (bounds) of the receiver. + @param view The view or window into whose coordinate system point is to be converted. + If view is nil, this method instead converts to window base coordinates. + @return The point converted to the coordinate system of view. + */ +- (CGPoint)yy_convertPoint:(CGPoint)point toViewOrWindow:(UIView *)view; + +/** + Converts a point from the coordinate system of a given view or window to that of the receiver. + + @param point A point specified in the local coordinate system (bounds) of view. + @param view The view or window with point in its coordinate system. + If view is nil, this method instead converts from window base coordinates. + @return The point converted to the local coordinate system (bounds) of the receiver. + */ +- (CGPoint)yy_convertPoint:(CGPoint)point fromViewOrWindow:(UIView *)view; + +/** + Converts a rectangle from the receiver's coordinate system to that of another view or window. + + @param rect A rectangle specified in the local coordinate system (bounds) of the receiver. + @param view The view or window that is the target of the conversion operation. If view is nil, this method instead converts to window base coordinates. + @return The converted rectangle. + */ +- (CGRect)yy_convertRect:(CGRect)rect toViewOrWindow:(UIView *)view; + +/** + Converts a rectangle from the coordinate system of another view or window to that of the receiver. + + @param rect A rectangle specified in the local coordinate system (bounds) of view. + @param view The view or window with rect in its coordinate system. + If view is nil, this method instead converts from window base coordinates. + @return The converted rectangle. + */ +- (CGRect)yy_convertRect:(CGRect)rect fromViewOrWindow:(UIView *)view; + +@end + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/YYLabel.h b/YYText/include/YYLabel.h new file mode 100644 index 00000000..a3687f12 --- /dev/null +++ b/YYText/include/YYLabel.h @@ -0,0 +1,380 @@ +// +// YYLabel.h +// YYText +// +// Created by ibireme on 15/2/25. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +#if __has_include() +#import +#import +#import +#else +#import "YYTextParser.h" +#import "YYTextLayout.h" +#import "YYTextAttribute.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + +#if !TARGET_INTERFACE_BUILDER + +/** + The YYLabel class implements a read-only text view. + + @discussion The API and behavior is similar to UILabel, but provides more features: + + * It supports asynchronous layout and rendering (to avoid blocking UI thread). + * It extends the CoreText attributes to support more text effects. + * It allows to add UIImage, UIView and CALayer as text attachments. + * It allows to add 'highlight' link to some range of text to allow user interact with. + * It allows to add container path and exclusion paths to control text container's shape. + * It supports vertical form layout to display CJK text. + + See NSAttributedString+YYText.h for more convenience methods to set the attributes. + See YYTextAttribute.h and YYTextLayout.h for more information. + */ +@interface YYLabel : UIView + +#pragma mark - Accessing the Text Attributes +///============================================================================= +/// @name Accessing the Text Attributes +///============================================================================= + +/** + The text displayed by the label. Default is nil. + Set a new value to this property also replaces the text in `attributedText`. + Get the value returns the plain text in `attributedText`. + */ +@property (nullable, nonatomic, copy) NSString *text; + +/** + The font of the text. Default is 17-point system font. + Set a new value to this property also causes the new font to be applied to the entire `attributedText`. + Get the value returns the font at the head of `attributedText`. + */ +@property (null_resettable, nonatomic, strong) UIFont *font; + +/** + The color of the text. Default is black. + Set a new value to this property also causes the new color to be applied to the entire `attributedText`. + Get the value returns the color at the head of `attributedText`. + */ +@property (null_resettable, nonatomic, strong) UIColor *textColor; + +/** + The shadow color of the text. Default is nil. + Set a new value to this property also causes the shadow color to be applied to the entire `attributedText`. + Get the value returns the shadow color at the head of `attributedText`. + */ +@property (nullable, nonatomic, strong) UIColor *shadowColor; + +/** + The shadow offset of the text. Default is CGSizeZero. + Set a new value to this property also causes the shadow offset to be applied to the entire `attributedText`. + Get the value returns the shadow offset at the head of `attributedText`. + */ +@property (nonatomic) CGSize shadowOffset; + +/** + The shadow blur of the text. Default is 0. + Set a new value to this property also causes the shadow blur to be applied to the entire `attributedText`. + Get the value returns the shadow blur at the head of `attributedText`. + */ +@property (nonatomic) CGFloat shadowBlurRadius; + +/** + The technique to use for aligning the text. Default is NSTextAlignmentNatural. + Set a new value to this property also causes the new alignment to be applied to the entire `attributedText`. + Get the value returns the alignment at the head of `attributedText`. + */ +@property (nonatomic) NSTextAlignment textAlignment; + +/** + The text vertical aligmnent in container. Default is YYTextVerticalAlignmentCenter. + */ +@property (nonatomic) YYTextVerticalAlignment textVerticalAlignment; + +/** + The styled text displayed by the label. + Set a new value to this property also replaces the value of the `text`, `font`, `textColor`, + `textAlignment` and other properties in label. + + @discussion It only support the attributes declared in CoreText and YYTextAttribute. + See `NSAttributedString+YYText` for more convenience methods to set the attributes. + */ +@property (nullable, nonatomic, copy) NSAttributedString *attributedText; + +/** + The technique to use for wrapping and truncating the label's text. + Default is NSLineBreakByTruncatingTail. + */ +@property (nonatomic) NSLineBreakMode lineBreakMode; + +/** + The truncation token string used when text is truncated. Default is nil. + When the value is nil, the label use "…" as default truncation token. + */ +@property (nullable, nonatomic, copy) NSAttributedString *truncationToken; + +/** + The maximum number of lines to use for rendering text. Default value is 1. + 0 means no limit. + */ +@property (nonatomic) NSUInteger numberOfLines; + +/** + When `text` or `attributedText` is changed, the parser will be called to modify the text. + It can be used to add code highlighting or emoticon replacement to text view. + The default value is nil. + + See `YYTextParser` protocol for more information. + */ +@property (nullable, nonatomic, strong) id textParser; + +/** + The current text layout in text view. It can be used to query the text layout information. + Set a new value to this property also replaces most properties in this label, such as `text`, + `color`, `attributedText`, `lineBreakMode`, `textContainerPath`, `exclusionPaths` and so on. + */ +@property (nullable, nonatomic, strong) YYTextLayout *textLayout; + + +#pragma mark - Configuring the Text Container +///============================================================================= +/// @name Configuring the Text Container +///============================================================================= + +/** + A UIBezierPath object that specifies the shape of the text frame. Default value is nil. + */ +@property (nullable, nonatomic, copy) UIBezierPath *textContainerPath; + +/** + An array of UIBezierPath objects representing the exclusion paths inside the + receiver's bounding rectangle. Default value is nil. + */ +@property (nullable, nonatomic, copy) NSArray *exclusionPaths; + +/** + The inset of the text container's layout area within the text view's content area. + Default value is UIEdgeInsetsZero. + */ +@property (nonatomic) UIEdgeInsets textContainerInset; + +/** + Whether the receiver's layout orientation is vertical form. Default is NO. + It may used to display CJK text. + */ +@property (nonatomic, getter=isVerticalForm) BOOL verticalForm; + +/** + The text line position modifier used to modify the lines' position in layout. + Default value is nil. + See `YYTextLinePositionModifier` protocol for more information. + */ +@property (nullable, nonatomic, copy) id linePositionModifier; + +/** + The debug option to display CoreText layout result. + The default value is [YYTextDebugOption sharedDebugOption]. + */ +@property (nullable, nonatomic, copy) YYTextDebugOption *debugOption; + + +#pragma mark - Getting the Layout Constraints +///============================================================================= +/// @name Getting the Layout Constraints +///============================================================================= + +/** + The preferred maximum width (in points) for a multiline label. + + @discussion This property affects the size of the label when layout constraints + are applied to it. During layout, if the text extends beyond the width + specified by this property, the additional text is flowed to one or more new + lines, thereby increasing the height of the label. If the text is vertical + form, this value will match to text height. + */ +@property (nonatomic) CGFloat preferredMaxLayoutWidth; + + +#pragma mark - Interacting with Text Data +///============================================================================= +/// @name Interacting with Text Data +///============================================================================= + +/** + When user tap the label, this action will be called (similar to tap gesture). + The default value is nil. + */ +@property (nullable, nonatomic, copy) YYTextAction textTapAction; + +/** + When user long press the label, this action will be called (similar to long press gesture). + The default value is nil. + */ +@property (nullable, nonatomic, copy) YYTextAction textLongPressAction; + +/** + When user tap the highlight range of text, this action will be called. + The default value is nil. + */ +@property (nullable, nonatomic, copy) YYTextAction highlightTapAction; + +/** + When user long press the highlight range of text, this action will be called. + The default value is nil. + */ +@property (nullable, nonatomic, copy) YYTextAction highlightLongPressAction; + + +#pragma mark - Configuring the Display Mode +///============================================================================= +/// @name Configuring the Display Mode +///============================================================================= + +/** + A Boolean value indicating whether the layout and rendering codes are running + asynchronously on background threads. + + The default value is `NO`. + */ +@property (nonatomic) BOOL displaysAsynchronously; + +/** + If the value is YES, and the layer is rendered asynchronously, then it will + set label.layer.contents to nil before display. + + The default value is `YES`. + + @discussion When the asynchronously display is enabled, the layer's content will + be updated after the background render process finished. If the render process + can not finished in a vsync time (1/60 second), the old content will be still kept + for display. You may manually clear the content by set the layer.contents to nil + after you update the label's properties, or you can just set this property to YES. + */ +@property (nonatomic) BOOL clearContentsBeforeAsynchronouslyDisplay; + +/** + If the value is YES, and the layer is rendered asynchronously, then it will add + a fade animation on layer when the contents of layer changed. + + The default value is `YES`. + */ +@property (nonatomic) BOOL fadeOnAsynchronouslyDisplay; + +/** + If the value is YES, then it will add a fade animation on layer when some range + of text become highlighted. + + The default value is `YES`. + */ +@property (nonatomic) BOOL fadeOnHighlight; + +/** + Ignore common properties (such as text, font, textColor, attributedText...) and + only use "textLayout" to display content. + + The default value is `NO`. + + @discussion If you control the label content only through "textLayout", then + you may set this value to YES for higher performance. + */ +@property (nonatomic) BOOL ignoreCommonProperties; + +/* + Tips: + + 1. If you only need a UILabel alternative to display rich text and receive link touch event, + you do not need to adjust the display mode properties. + + 2. If you have performance issues, you may enable the asynchronous display mode + by setting the `displaysAsynchronously` to YES. + + 3. If you want to get the highest performance, you should do text layout with + `YYTextLayout` class in background thread. Here's an example: + + YYLabel *label = [YYLabel new]; + label.displaysAsynchronously = YES; + label.ignoreCommonProperties = YES; + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + + // Create attributed string. + NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:@"Some Text"]; + text.yy_font = [UIFont systemFontOfSize:16]; + text.yy_color = [UIColor grayColor]; + [text yy_setColor:[UIColor redColor] range:NSMakeRange(0, 4)]; + + // Create text container + YYTextContainer *container = [YYTextContainer new]; + container.size = CGSizeMake(100, CGFLOAT_MAX); + container.maximumNumberOfRows = 0; + + // Generate a text layout. + YYTextLayout *layout = [YYTextLayout layoutWithContainer:container text:text]; + + dispatch_async(dispatch_get_main_queue(), ^{ + label.size = layout.textBoundingSize; + label.textLayout = layout; + }); + }); + + */ + +@end + + +#else // TARGET_INTERFACE_BUILDER +IB_DESIGNABLE +@interface YYLabel : UIView +@property (nullable, nonatomic, copy) IBInspectable NSString *text; +@property (null_resettable, nonatomic, strong) IBInspectable UIColor *textColor; +@property (nullable, nonatomic, strong) IBInspectable NSString *fontName_; +@property (nonatomic) IBInspectable CGFloat fontSize_; +@property (nonatomic) IBInspectable BOOL fontIsBold_; +@property (nonatomic) IBInspectable NSUInteger numberOfLines; +@property (nonatomic) IBInspectable NSInteger lineBreakMode; +@property (nonatomic) IBInspectable CGFloat preferredMaxLayoutWidth; +@property (nonatomic, getter=isVerticalForm) IBInspectable BOOL verticalForm; +@property (nonatomic) IBInspectable NSInteger textAlignment; +@property (nonatomic) IBInspectable NSInteger textVerticalAlignment; +@property (nullable, nonatomic, strong) IBInspectable UIColor *shadowColor; +@property (nonatomic) IBInspectable CGPoint shadowOffset; +@property (nonatomic) IBInspectable CGFloat shadowBlurRadius; +@property (nullable, nonatomic, copy) IBInspectable NSAttributedString *attributedText; +@property (nonatomic) IBInspectable CGFloat insetTop_; +@property (nonatomic) IBInspectable CGFloat insetBottom_; +@property (nonatomic) IBInspectable CGFloat insetLeft_; +@property (nonatomic) IBInspectable CGFloat insetRight_; +@property (nonatomic) IBInspectable BOOL debugEnabled_; + +@property (null_resettable, nonatomic, strong) UIFont *font; +@property (nullable, nonatomic, copy) NSAttributedString *truncationToken; +@property (nullable, nonatomic, strong) id textParser; +@property (nullable, nonatomic, strong) YYTextLayout *textLayout; +@property (nullable, nonatomic, copy) UIBezierPath *textContainerPath; +@property (nullable, nonatomic, copy) NSArray *exclusionPaths; +@property (nonatomic) UIEdgeInsets textContainerInset; +@property (nullable, nonatomic, copy) id linePositionModifier; +@property (nonnull, nonatomic, copy) YYTextDebugOption *debugOption; +@property (nullable, nonatomic, copy) YYTextAction textTapAction; +@property (nullable, nonatomic, copy) YYTextAction textLongPressAction; +@property (nullable, nonatomic, copy) YYTextAction highlightTapAction; +@property (nullable, nonatomic, copy) YYTextAction highlightLongPressAction; +@property (nonatomic) BOOL displaysAsynchronously; +@property (nonatomic) BOOL clearContentsBeforeAsynchronouslyDisplay; +@property (nonatomic) BOOL fadeOnAsynchronouslyDisplay; +@property (nonatomic) BOOL fadeOnHighlight; +@property (nonatomic) BOOL ignoreCommonProperties; +@end +#endif // !TARGET_INTERFACE_BUILDER + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/YYText.h b/YYText/include/YYText.h new file mode 100644 index 00000000..0900d8b9 --- /dev/null +++ b/YYText/include/YYText.h @@ -0,0 +1,50 @@ +// +// YYText.h +// YYText +// +// Created by ibireme on 15/2/25. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +#if __has_include() +FOUNDATION_EXPORT double YYTextVersionNumber; +FOUNDATION_EXPORT const unsigned char YYTextVersionString[]; +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#else +#import "YYLabel.h" +#import "YYTextView.h" +#import "YYTextAttribute.h" +#import "YYTextArchiver.h" +#import "YYTextParser.h" +#import "YYTextRunDelegate.h" +#import "YYTextRubyAnnotation.h" +#import "YYTextLayout.h" +#import "YYTextLine.h" +#import "YYTextInput.h" +#import "YYTextDebugOption.h" +#import "YYTextKeyboardManager.h" +#import "YYTextUtilities.h" +#import "NSAttributedString+YYText.h" +#import "NSParagraphStyle+YYText.h" +#import "UIPasteboard+YYText.h" +#endif diff --git a/YYText/include/YYTextArchiver.h b/YYText/include/YYTextArchiver.h new file mode 100644 index 00000000..cff8935f --- /dev/null +++ b/YYText/include/YYTextArchiver.h @@ -0,0 +1,33 @@ +// +// YYTextArchiver.h +// YYText +// +// Created by ibireme on 15/3/16. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + A subclass of `NSKeyedArchiver` which implement `NSKeyedArchiverDelegate` protocol. + + The archiver can encode the object which contains + CGColor/CGImage/CTRunDelegateRef/.. (such as NSAttributedString). + */ +@interface YYTextArchiver : NSKeyedArchiver +@end + +/** + A subclass of `NSKeyedUnarchiver` which implement `NSKeyedUnarchiverDelegate` + protocol. The unarchiver can decode the data which is encoded by + `YYTextArchiver` or `NSKeyedArchiver`. + */ +@interface YYTextUnarchiver : NSKeyedUnarchiver +@end + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/YYTextAsyncLayer.h b/YYText/include/YYTextAsyncLayer.h new file mode 100644 index 00000000..bf8c16b8 --- /dev/null +++ b/YYText/include/YYTextAsyncLayer.h @@ -0,0 +1,79 @@ +// +// YYTextAsyncLayer.h +// YYText +// +// Created by ibireme on 15/4/11. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import +#import + +@class YYTextAsyncLayerDisplayTask; + +NS_ASSUME_NONNULL_BEGIN + +/** + The YYTextAsyncLayer class is a subclass of CALayer used for render contents asynchronously. + + @discussion When the layer need update it's contents, it will ask the delegate + for a async display task to render the contents in a background queue. + */ +@interface YYTextAsyncLayer : CALayer +/// Whether the render code is executed in background. Default is YES. +@property BOOL displaysAsynchronously; +@end + + +/** + The YYTextAsyncLayer's delegate protocol. The delegate of the YYTextAsyncLayer (typically a UIView) + must implements the method in this protocol. + */ +@protocol YYTextAsyncLayerDelegate +@required +/// This method is called to return a new display task when the layer's contents need update. +- (YYTextAsyncLayerDisplayTask *)newAsyncDisplayTask; +@end + + +/** + A display task used by YYTextAsyncLayer to render the contents in background queue. + */ +@interface YYTextAsyncLayerDisplayTask : NSObject + +/** + This block will be called before the asynchronous drawing begins. + It will be called on the main thread. + + block param layer: The layer. + */ +@property (nullable, nonatomic, copy) void (^willDisplay)(CALayer *layer); + +/** + This block is called to draw the layer's contents. + + @discussion This block may be called on main thread or background thread, + so is should be thread-safe. + + block param context: A new bitmap content created by layer. + block param size: The content size (typically same as layer's bound size). + block param isCancelled: If this block returns `YES`, the method should cancel the + drawing process and return as quickly as possible. + */ +@property (nullable, nonatomic, copy) void (^display)(CGContextRef context, CGSize size, BOOL(^isCancelled)(void)); + +/** + This block will be called after the asynchronous drawing finished. + It will be called on the main thread. + + block param layer: The layer. + block param finished: If the draw process is cancelled, it's `NO`, otherwise it's `YES`; + */ +@property (nullable, nonatomic, copy) void (^didDisplay)(CALayer *layer, BOOL finished); + +@end + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/YYTextAttribute.h b/YYText/include/YYTextAttribute.h new file mode 100644 index 00000000..c6bb774e --- /dev/null +++ b/YYText/include/YYTextAttribute.h @@ -0,0 +1,347 @@ +// +// YYTextAttribute.h +// YYText +// +// Created by ibireme on 14/10/26. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - Enum Define + +/// The attribute type +typedef NS_OPTIONS(NSInteger, YYTextAttributeType) { + YYTextAttributeTypeNone = 0, + YYTextAttributeTypeUIKit = 1 << 0, ///< UIKit attributes, such as UILabel/UITextField/drawInRect. + YYTextAttributeTypeCoreText = 1 << 1, ///< CoreText attributes, used by CoreText. + YYTextAttributeTypeYYText = 1 << 2, ///< YYText attributes, used by YYText. +}; + +/// Get the attribute type from an attribute name. +extern YYTextAttributeType YYTextAttributeGetType(NSString *attributeName); + +/** + Line style in YYText (similar to NSUnderlineStyle). + */ +typedef NS_OPTIONS (NSInteger, YYTextLineStyle) { + // basic style (bitmask:0xFF) + YYTextLineStyleNone = 0x00, ///< ( ) Do not draw a line (Default). + YYTextLineStyleSingle = 0x01, ///< (──────) Draw a single line. + YYTextLineStyleThick = 0x02, ///< (━━━━━━━) Draw a thick line. + YYTextLineStyleDouble = 0x09, ///< (══════) Draw a double line. + + // style pattern (bitmask:0xF00) + YYTextLineStylePatternSolid = 0x000, ///< (────────) Draw a solid line (Default). + YYTextLineStylePatternDot = 0x100, ///< (‑ ‑ ‑ ‑ ‑ ‑) Draw a line of dots. + YYTextLineStylePatternDash = 0x200, ///< (— — — —) Draw a line of dashes. + YYTextLineStylePatternDashDot = 0x300, ///< (— ‑ — ‑ — ‑) Draw a line of alternating dashes and dots. + YYTextLineStylePatternDashDotDot = 0x400, ///< (— ‑ ‑ — ‑ ‑) Draw a line of alternating dashes and two dots. + YYTextLineStylePatternCircleDot = 0x900, ///< (••••••••••••) Draw a line of small circle dots. +}; + +/** + Text vertical alignment. + */ +typedef NS_ENUM(NSInteger, YYTextVerticalAlignment) { + YYTextVerticalAlignmentTop = 0, ///< Top alignment. + YYTextVerticalAlignmentCenter = 1, ///< Center alignment. + YYTextVerticalAlignmentBottom = 2, ///< Bottom alignment. +}; + +/** + The direction define in YYText. + */ +typedef NS_OPTIONS(NSUInteger, YYTextDirection) { + YYTextDirectionNone = 0, + YYTextDirectionTop = 1 << 0, + YYTextDirectionRight = 1 << 1, + YYTextDirectionBottom = 1 << 2, + YYTextDirectionLeft = 1 << 3, +}; + +/** + The trunction type, tells the truncation engine which type of truncation is being requested. + */ +typedef NS_ENUM (NSUInteger, YYTextTruncationType) { + /// No truncate. + YYTextTruncationTypeNone = 0, + + /// Truncate at the beginning of the line, leaving the end portion visible. + YYTextTruncationTypeStart = 1, + + /// Truncate at the end of the line, leaving the start portion visible. + YYTextTruncationTypeEnd = 2, + + /// Truncate in the middle of the line, leaving both the start and the end portions visible. + YYTextTruncationTypeMiddle = 3, +}; + + + +#pragma mark - Attribute Name Defined in YYText + +/// The value of this attribute is a `YYTextBackedString` object. +/// Use this attribute to store the original plain text if it is replaced by something else (such as attachment). +UIKIT_EXTERN NSString *const YYTextBackedStringAttributeName; + +/// The value of this attribute is a `YYTextBinding` object. +/// Use this attribute to bind a range of text together, as if it was a single charactor. +UIKIT_EXTERN NSString *const YYTextBindingAttributeName; + +/// The value of this attribute is a `YYTextShadow` object. +/// Use this attribute to add shadow to a range of text. +/// Shadow will be drawn below text glyphs. Use YYTextShadow.subShadow to add multi-shadow. +UIKIT_EXTERN NSString *const YYTextShadowAttributeName; + +/// The value of this attribute is a `YYTextShadow` object. +/// Use this attribute to add inner shadow to a range of text. +/// Inner shadow will be drawn above text glyphs. Use YYTextShadow.subShadow to add multi-shadow. +UIKIT_EXTERN NSString *const YYTextInnerShadowAttributeName; + +/// The value of this attribute is a `YYTextDecoration` object. +/// Use this attribute to add underline to a range of text. +/// The underline will be drawn below text glyphs. +UIKIT_EXTERN NSString *const YYTextUnderlineAttributeName; + +/// The value of this attribute is a `YYTextDecoration` object. +/// Use this attribute to add strikethrough (delete line) to a range of text. +/// The strikethrough will be drawn above text glyphs. +UIKIT_EXTERN NSString *const YYTextStrikethroughAttributeName; + +/// The value of this attribute is a `YYTextBorder` object. +/// Use this attribute to add cover border or cover color to a range of text. +/// The border will be drawn above the text glyphs. +UIKIT_EXTERN NSString *const YYTextBorderAttributeName; + +/// The value of this attribute is a `YYTextBorder` object. +/// Use this attribute to add background border or background color to a range of text. +/// The border will be drawn below the text glyphs. +UIKIT_EXTERN NSString *const YYTextBackgroundBorderAttributeName; + +/// The value of this attribute is a `YYTextBorder` object. +/// Use this attribute to add a code block border to one or more line of text. +/// The border will be drawn below the text glyphs. +UIKIT_EXTERN NSString *const YYTextBlockBorderAttributeName; + +/// The value of this attribute is a `YYTextAttachment` object. +/// Use this attribute to add attachment to text. +/// It should be used in conjunction with a CTRunDelegate. +UIKIT_EXTERN NSString *const YYTextAttachmentAttributeName; + +/// The value of this attribute is a `YYTextHighlight` object. +/// Use this attribute to add a touchable highlight state to a range of text. +UIKIT_EXTERN NSString *const YYTextHighlightAttributeName; + +/// The value of this attribute is a `NSValue` object stores CGAffineTransform. +/// Use this attribute to add transform to each glyph in a range of text. +UIKIT_EXTERN NSString *const YYTextGlyphTransformAttributeName; + + + +#pragma mark - String Token Define + +UIKIT_EXTERN NSString *const YYTextAttachmentToken; ///< Object replacement character (U+FFFC), used for text attachment. +UIKIT_EXTERN NSString *const YYTextTruncationToken; ///< Horizontal ellipsis (U+2026), used for text truncation "…". + + + +#pragma mark - Attribute Value Define + +/** + The tap/long press action callback defined in YYText. + + @param containerView The text container view (such as YYLabel/YYTextView). + @param text The whole text. + @param range The text range in `text` (if no range, the range.location is NSNotFound). + @param rect The text frame in `containerView` (if no data, the rect is CGRectNull). + */ +typedef void(^YYTextAction)(UIView *containerView, NSAttributedString *text, NSRange range, CGRect rect); + + +/** + YYTextBackedString objects are used by the NSAttributedString class cluster + as the values for text backed string attributes (stored in the attributed + string under the key named YYTextBackedStringAttributeName). + + It may used for copy/paste plain text from attributed string. + Example: If :) is replace by a custom emoji (such as😊), the backed string can be set to @":)". + */ +@interface YYTextBackedString : NSObject ++ (instancetype)stringWithString:(nullable NSString *)string; +@property (nullable, nonatomic, copy) NSString *string; ///< backed string +@end + + +/** + YYTextBinding objects are used by the NSAttributedString class cluster + as the values for shadow attributes (stored in the attributed string under + the key named YYTextBindingAttributeName). + + Add this to a range of text will make the specified characters 'binding together'. + YYTextView will treat the range of text as a single character during text + selection and edit. + */ +@interface YYTextBinding : NSObject ++ (instancetype)bindingWithDeleteConfirm:(BOOL)deleteConfirm; +@property (nonatomic) BOOL deleteConfirm; ///< confirm the range when delete in YYTextView +@end + + +/** + YYTextShadow objects are used by the NSAttributedString class cluster + as the values for shadow attributes (stored in the attributed string under + the key named YYTextShadowAttributeName or YYTextInnerShadowAttributeName). + + It's similar to `NSShadow`, but offers more options. + */ +@interface YYTextShadow : NSObject ++ (instancetype)shadowWithColor:(nullable UIColor *)color offset:(CGSize)offset radius:(CGFloat)radius; + +@property (nullable, nonatomic, strong) UIColor *color; ///< shadow color +@property (nonatomic) CGSize offset; ///< shadow offset +@property (nonatomic) CGFloat radius; ///< shadow blur radius +@property (nonatomic) CGBlendMode blendMode; ///< shadow blend mode +@property (nullable, nonatomic, strong) YYTextShadow *subShadow; ///< a sub shadow which will be added above the parent shadow + ++ (instancetype)shadowWithNSShadow:(NSShadow *)nsShadow; ///< convert NSShadow to YYTextShadow +- (NSShadow *)nsShadow; ///< convert YYTextShadow to NSShadow +@end + + +/** + YYTextDecorationLine objects are used by the NSAttributedString class cluster + as the values for decoration line attributes (stored in the attributed string under + the key named YYTextUnderlineAttributeName or YYTextStrikethroughAttributeName). + + When it's used as underline, the line is drawn below text glyphs; + when it's used as strikethrough, the line is drawn above text glyphs. + */ +@interface YYTextDecoration : NSObject ++ (instancetype)decorationWithStyle:(YYTextLineStyle)style; ++ (instancetype)decorationWithStyle:(YYTextLineStyle)style width:(nullable NSNumber *)width color:(nullable UIColor *)color; +@property (nonatomic) YYTextLineStyle style; ///< line style +@property (nullable, nonatomic, strong) NSNumber *width; ///< line width (nil means automatic width) +@property (nullable, nonatomic, strong) UIColor *color; ///< line color (nil means automatic color) +@property (nullable, nonatomic, strong) YYTextShadow *shadow; ///< line shadow +@end + + +/** + YYTextBorder objects are used by the NSAttributedString class cluster + as the values for border attributes (stored in the attributed string under + the key named YYTextBorderAttributeName or YYTextBackgroundBorderAttributeName). + + It can be used to draw a border around a range of text, or draw a background + to a range of text. + + Example: + ╭──────╮ + │ Text │ + ╰──────╯ + */ +@interface YYTextBorder : NSObject ++ (instancetype)borderWithLineStyle:(YYTextLineStyle)lineStyle lineWidth:(CGFloat)width strokeColor:(nullable UIColor *)color; ++ (instancetype)borderWithFillColor:(nullable UIColor *)color cornerRadius:(CGFloat)cornerRadius; +@property (nonatomic) YYTextLineStyle lineStyle; ///< border line style +@property (nonatomic) CGFloat strokeWidth; ///< border line width +@property (nullable, nonatomic, strong) UIColor *strokeColor; ///< border line color +@property (nonatomic) CGLineJoin lineJoin; ///< border line join +@property (nonatomic) UIEdgeInsets insets; ///< border insets for text bounds +@property (nonatomic) CGFloat cornerRadius; ///< border corder radius +@property (nullable, nonatomic, strong) YYTextShadow *shadow; ///< border shadow +@property (nullable, nonatomic, strong) UIColor *fillColor; ///< inner fill color +@end + + +/** + YYTextAttachment objects are used by the NSAttributedString class cluster + as the values for attachment attributes (stored in the attributed string under + the key named YYTextAttachmentAttributeName). + + When display an attributed string which contains `YYTextAttachment` object, + the content will be placed in text metric. If the content is `UIImage`, + then it will be drawn to CGContext; if the content is `UIView` or `CALayer`, + then it will be added to the text container's view or layer. + */ +@interface YYTextAttachment : NSObject ++ (instancetype)attachmentWithContent:(nullable id)content; +@property (nullable, nonatomic, strong) id content; ///< Supported type: UIImage, UIView, CALayer +@property (nonatomic) UIViewContentMode contentMode; ///< Content display mode. +@property (nonatomic) UIEdgeInsets contentInsets; ///< The insets when drawing content. +@property (nullable, nonatomic, strong) NSDictionary *userInfo; ///< The user information dictionary. +@end + + +/** + YYTextHighlight objects are used by the NSAttributedString class cluster + as the values for touchable highlight attributes (stored in the attributed string + under the key named YYTextHighlightAttributeName). + + When display an attributed string in `YYLabel` or `YYTextView`, the range of + highlight text can be toucheds down by users. If a range of text is turned into + highlighted state, the `attributes` in `YYTextHighlight` will be used to modify + (set or remove) the original attributes in the range for display. + */ +@interface YYTextHighlight : NSObject + +/** + Attributes that you can apply to text in an attributed string when highlight. + Key: Same as CoreText/YYText Attribute Name. + Value: Modify attribute value when highlight (NSNull for remove attribute). + */ +@property (nullable, nonatomic, copy) NSDictionary *attributes; + +/** + Creates a highlight object with specified attributes. + + @param attributes The attributes which will replace original attributes when highlight, + If the value is NSNull, it will removed when highlight. + */ ++ (instancetype)highlightWithAttributes:(nullable NSDictionary *)attributes; + +/** + Convenience methods to create a default highlight with the specifeid background color. + + @param color The background border color. + */ ++ (instancetype)highlightWithBackgroundColor:(nullable UIColor *)color; + +// Convenience methods below to set the `attributes`. +- (void)setFont:(nullable UIFont *)font; +- (void)setColor:(nullable UIColor *)color; +- (void)setStrokeWidth:(nullable NSNumber *)width; +- (void)setStrokeColor:(nullable UIColor *)color; +- (void)setShadow:(nullable YYTextShadow *)shadow; +- (void)setInnerShadow:(nullable YYTextShadow *)shadow; +- (void)setUnderline:(nullable YYTextDecoration *)underline; +- (void)setStrikethrough:(nullable YYTextDecoration *)strikethrough; +- (void)setBackgroundBorder:(nullable YYTextBorder *)border; +- (void)setBorder:(nullable YYTextBorder *)border; +- (void)setAttachment:(nullable YYTextAttachment *)attachment; + +/** + The user information dictionary, default is nil. + */ +@property (nullable, nonatomic, copy) NSDictionary *userInfo; + +/** + Tap action when user tap the highlight, default is nil. + If the value is nil, YYTextView or YYLabel will ask it's delegate to handle the tap action. + */ +@property (nullable, nonatomic, copy) YYTextAction tapAction; + +/** + Long press action when user long press the highlight, default is nil. + If the value is nil, YYTextView or YYLabel will ask it's delegate to handle the long press action. + */ +@property (nullable, nonatomic, copy) YYTextAction longPressAction; + +@end + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/YYTextContainerView.h b/YYText/include/YYTextContainerView.h new file mode 100644 index 00000000..66cd909f --- /dev/null +++ b/YYText/include/YYTextContainerView.h @@ -0,0 +1,55 @@ +// +// YYTextContainerView.h +// YYText +// +// Created by ibireme on 15/4/21. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +#if __has_include() +#import +#else +#import "YYTextLayout.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + A simple view to diaplay `YYTextLayout`. + + @discussion This view can become first responder. If this view is first responder, + all the action (such as UIMenu's action) would forward to the `hostView` property. + Typically, you should not use this class directly. + + @warning All the methods in this class should be called on main thread. + */ +@interface YYTextContainerView : UIView + +/// First responder's aciton will forward to this view. +@property (nullable, nonatomic, weak) UIView *hostView; + +/// Debug option for layout debug. Set this property will let the view redraw it's contents. +@property (nullable, nonatomic, copy) YYTextDebugOption *debugOption; + +/// Text vertical alignment. +@property (nonatomic) YYTextVerticalAlignment textVerticalAlignment; + +/// Text layout. Set this property will let the view redraw it's contents. +@property (nullable, nonatomic, strong) YYTextLayout *layout; + +/// The contents fade animation duration when the layout's contents changed. Default is 0 (no animation). +@property (nonatomic) NSTimeInterval contentsFadeDuration; + +/// Convenience method to set `layout` and `contentsFadeDuration`. +/// @param layout Same as `layout` property. +/// @param fadeDuration Same as `contentsFadeDuration` property. +- (void)setLayout:(nullable YYTextLayout *)layout withFadeDuration:(NSTimeInterval)fadeDuration; + +@end + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/YYTextDebugOption.h b/YYText/include/YYTextDebugOption.h new file mode 100644 index 00000000..f7063510 --- /dev/null +++ b/YYText/include/YYTextDebugOption.h @@ -0,0 +1,95 @@ +// +// YYTextDebugOption.h +// YYText +// +// Created by ibireme on 15/4/8. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +@class YYTextDebugOption; + +NS_ASSUME_NONNULL_BEGIN + +/** + The YYTextDebugTarget protocol defines the method a debug target should implement. + A debug target can be add to the global container to receive the shared debug + option changed notification. + */ +@protocol YYTextDebugTarget + +@required +/** + When the shared debug option changed, this method would be called on main thread. + It should return as quickly as possible. The option's property should not be changed + in this method. + + @param option The shared debug option. + */ +- (void)setDebugOption:(nullable YYTextDebugOption *)option; +@end + + + +/** + The debug option for YYText. + */ +@interface YYTextDebugOption : NSObject +@property (nullable, nonatomic, strong) UIColor *baselineColor; ///< baseline color +@property (nullable, nonatomic, strong) UIColor *CTFrameBorderColor; ///< CTFrame path border color +@property (nullable, nonatomic, strong) UIColor *CTFrameFillColor; ///< CTFrame path fill color +@property (nullable, nonatomic, strong) UIColor *CTLineBorderColor; ///< CTLine bounds border color +@property (nullable, nonatomic, strong) UIColor *CTLineFillColor; ///< CTLine bounds fill color +@property (nullable, nonatomic, strong) UIColor *CTLineNumberColor; ///< CTLine line number color +@property (nullable, nonatomic, strong) UIColor *CTRunBorderColor; ///< CTRun bounds border color +@property (nullable, nonatomic, strong) UIColor *CTRunFillColor; ///< CTRun bounds fill color +@property (nullable, nonatomic, strong) UIColor *CTRunNumberColor; ///< CTRun number color +@property (nullable, nonatomic, strong) UIColor *CGGlyphBorderColor; ///< CGGlyph bounds border color +@property (nullable, nonatomic, strong) UIColor *CGGlyphFillColor; ///< CGGlyph bounds fill color + +- (BOOL)needDrawDebug; ///< `YES`: at least one debug color is visible. `NO`: all debug color is invisible/nil. +- (void)clear; ///< Set all debug color to nil. + +/** + Add a debug target. + + @discussion When `setSharedDebugOption:` is called, all added debug target will + receive `setDebugOption:` in main thread. It maintains an unsafe_unretained + reference to this target. The target must to removed before dealloc. + + @param target A debug target. + */ ++ (void)addDebugTarget:(id)target; + +/** + Remove a debug target which is added by `addDebugTarget:`. + + @param target A debug target. + */ ++ (void)removeDebugTarget:(id)target; + +/** + Returns the shared debug option. + + @return The shared debug option, default is nil. + */ ++ (nullable YYTextDebugOption *)sharedDebugOption; + +/** + Set a debug option as shared debug option. + This method must be called on main thread. + + @discussion When call this method, the new option will set to all debug target + which is added by `addDebugTarget:`. + + @param option A new debug option (nil is valid). + */ ++ (void)setSharedDebugOption:(nullable YYTextDebugOption *)option; + +@end + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/YYTextEffectWindow.h b/YYText/include/YYTextEffectWindow.h new file mode 100644 index 00000000..a2780999 --- /dev/null +++ b/YYText/include/YYTextEffectWindow.h @@ -0,0 +1,52 @@ +// +// YYTextEffectWindow.h +// YYText +// +// Created by ibireme on 15/2/25. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +#if __has_include() +#import +#import +#else +#import "YYTextMagnifier.h" +#import "YYTextSelectionView.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + A window to display magnifier and extra contents for text view. + + @discussion Use `sharedWindow` to get the instance, don't create your own instance. + Typically, you should not use this class directly. + */ +@interface YYTextEffectWindow : UIWindow + +/// Returns the shared instance (returns nil in App Extension). ++ (nullable instancetype)sharedWindow; + +/// Show the magnifier in this window with a 'popup' animation. @param mag A magnifier. +- (void)showMagnifier:(YYTextMagnifier *)mag; +/// Update the magnifier content and position. @param mag A magnifier. +- (void)moveMagnifier:(YYTextMagnifier *)mag; +/// Remove the magnifier from this window with a 'shrink' animation. @param mag A magnifier. +- (void)hideMagnifier:(YYTextMagnifier *)mag; + + +/// Show the selection dot in this window if the dot is clipped by the selection view. +/// @param selection A selection view. +- (void)showSelectionDot:(YYTextSelectionView *)selection; +/// Remove the selection dot from this window. +/// @param selection A selection view. +- (void)hideSelectionDot:(YYTextSelectionView *)selection; + +@end + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/YYTextInput.h b/YYText/include/YYTextInput.h new file mode 100644 index 00000000..2e879c09 --- /dev/null +++ b/YYText/include/YYTextInput.h @@ -0,0 +1,87 @@ +// +// YYTextInput.h +// YYText +// +// Created by ibireme on 15/4/17. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Text position affinity. For example, the offset appears after the last + character on a line is backward affinity, before the first character on + the following line is forward affinity. + */ +typedef NS_ENUM(NSInteger, YYTextAffinity) { + YYTextAffinityForward = 0, ///< offset appears before the character + YYTextAffinityBackward = 1, ///< offset appears after the character +}; + + +/** + A YYTextPosition object represents a position in a text container; in other words, + it is an index into the backing string in a text-displaying view. + + YYTextPosition has the same API as Apple's implementation in UITextView/UITextField, + so you can alse use it to interact with UITextView/UITextField. + */ +@interface YYTextPosition : UITextPosition + +@property (nonatomic, readonly) NSInteger offset; +@property (nonatomic, readonly) YYTextAffinity affinity; + ++ (instancetype)positionWithOffset:(NSInteger)offset; ++ (instancetype)positionWithOffset:(NSInteger)offset affinity:(YYTextAffinity) affinity; + +- (NSComparisonResult)compare:(id)otherPosition; + +@end + + +/** + A YYTextRange object represents a range of characters in a text container; in other words, + it identifies a starting index and an ending index in string backing a text-displaying view. + + YYTextRange has the same API as Apple's implementation in UITextView/UITextField, + so you can alse use it to interact with UITextView/UITextField. + */ +@interface YYTextRange : UITextRange + +@property (nonatomic, readonly) YYTextPosition *start; +@property (nonatomic, readonly) YYTextPosition *end; +@property (nonatomic, readonly, getter=isEmpty) BOOL empty; + ++ (instancetype)rangeWithRange:(NSRange)range; ++ (instancetype)rangeWithRange:(NSRange)range affinity:(YYTextAffinity) affinity; ++ (instancetype)rangeWithStart:(YYTextPosition *)start end:(YYTextPosition *)end; ++ (instancetype)defaultRange; ///< <{0,0} Forward> + +- (NSRange)asRange; + +@end + + +/** + A YYTextSelectionRect object encapsulates information about a selected range of + text in a text-displaying view. + + YYTextSelectionRect has the same API as Apple's implementation in UITextView/UITextField, + so you can alse use it to interact with UITextView/UITextField. + */ +@interface YYTextSelectionRect : UITextSelectionRect + +@property (nonatomic, readwrite) CGRect rect; +@property (nonatomic, readwrite) UITextWritingDirection writingDirection; +@property (nonatomic, readwrite) BOOL containsStart; +@property (nonatomic, readwrite) BOOL containsEnd; +@property (nonatomic, readwrite) BOOL isVertical; + +@end + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/YYTextKeyboardManager.h b/YYText/include/YYTextKeyboardManager.h new file mode 100644 index 00000000..5f28a557 --- /dev/null +++ b/YYText/include/YYTextKeyboardManager.h @@ -0,0 +1,98 @@ +// +// YYTextKeyboardManager.h +// YYText +// +// Created by ibireme on 15/6/3. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + System keyboard transition information. + Use -[YYTextKeyboardManager convertRect:toView:] to convert frame to specified view. + */ +typedef struct { + BOOL fromVisible; ///< Keyboard visible before transition. + BOOL toVisible; ///< Keyboard visible after transition. + CGRect fromFrame; ///< Keyboard frame before transition. + CGRect toFrame; ///< Keyboard frame after transition. + NSTimeInterval animationDuration; ///< Keyboard transition animation duration. + UIViewAnimationCurve animationCurve; ///< Keyboard transition animation curve. + UIViewAnimationOptions animationOption; ///< Keybaord transition animation option. +} YYTextKeyboardTransition; + + +/** + The YYTextKeyboardObserver protocol defines the method you can use + to receive system keyboard change information. + */ +@protocol YYTextKeyboardObserver +@optional +- (void)keyboardChangedWithTransition:(YYTextKeyboardTransition)transition; +@end + + +/** + A YYTextKeyboardManager object lets you get the system keyboard information, + and track the keyboard visible/frame/transition. + + @discussion You should access this class in main thread. + Compatible: iPhone/iPad with iOS6/7/8/9. + */ +@interface YYTextKeyboardManager : NSObject + +- (instancetype)init UNAVAILABLE_ATTRIBUTE; ++ (instancetype)new UNAVAILABLE_ATTRIBUTE; + +/// Get the default manager (returns nil in App Extension). ++ (nullable instancetype)defaultManager; + +/// Get the keyboard window. nil if there's no keyboard window. +@property (nullable, nonatomic, readonly) UIWindow *keyboardWindow; + +/// Get the keyboard view. nil if there's no keyboard view. +@property (nullable, nonatomic, readonly) UIView *keyboardView; + +/// Whether the keyboard is visible. +@property (nonatomic, readonly, getter=isKeyboardVisible) BOOL keyboardVisible; + +/// Get the keyboard frame. CGRectNull if there's no keyboard view. +/// Use convertRect:toView: to convert frame to specified view. +@property (nonatomic, readonly) CGRect keyboardFrame; + + +/** + Add an observer to manager to get keyboard change information. + This method makes a weak reference to the observer. + + @param observer An observer. + This method will do nothing if the observer is nil, or already added. + */ +- (void)addObserver:(id)observer; + +/** + Remove an observer from manager. + + @param observer An observer. + This method will do nothing if the observer is nil, or not in manager. + */ +- (void)removeObserver:(id)observer; + +/** + Convert rect to specified view or window. + + @param rect The frame rect. + @param view A specified view or window (pass nil to convert for main window). + @return The converted rect in specifeid view. + */ +- (CGRect)convertRect:(CGRect)rect toView:(nullable UIView *)view; + +@end + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/YYTextLayout.h b/YYText/include/YYTextLayout.h new file mode 100644 index 00000000..f9008223 --- /dev/null +++ b/YYText/include/YYTextLayout.h @@ -0,0 +1,571 @@ +// +// YYTextLayout.h +// YYText +// +// Created by ibireme on 15/3/3. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import +#import + +#if __has_include() +#import +#import +#import +#else +#import "YYTextDebugOption.h" +#import "YYTextLine.h" +#import "YYTextInput.h" +#endif + +@protocol YYTextLinePositionModifier; + +NS_ASSUME_NONNULL_BEGIN + +/** + The max text container size in layout. + */ +extern const CGSize YYTextContainerMaxSize; + +/** + The YYTextContainer class defines a region in which text is laid out. + YYTextLayout class uses one or more YYTextContainer objects to generate layouts. + + A YYTextContainer defines rectangular regions (`size` and `insets`) or + nonrectangular shapes (`path`), and you can define exclusion paths inside the + text container's bounding rectangle so that text flows around the exclusion + path as it is laid out. + + All methods in this class is thread-safe. + + Example: + + ┌─────────────────────────────┐ <------- container + │ │ + │ asdfasdfasdfasdfasdfa <------------ container insets + │ asdfasdfa asdfasdfa │ + │ asdfas asdasd │ + │ asdfa <----------------------- container exclusion path + │ asdfas adfasd │ + │ asdfasdfa asdfasdfa │ + │ asdfasdfasdfasdfasdfa │ + │ │ + └─────────────────────────────┘ + */ +@interface YYTextContainer : NSObject + +/// Creates a container with the specified size. @param size The size. ++ (instancetype)containerWithSize:(CGSize)size; + +/// Creates a container with the specified size and insets. @param size The size. @param insets The text insets. ++ (instancetype)containerWithSize:(CGSize)size insets:(UIEdgeInsets)insets; + +/// Creates a container with the specified path. @param path The path. ++ (instancetype)containerWithPath:(nullable UIBezierPath *)path; + +/// The constrained size. (if the size is larger than YYTextContainerMaxSize, it will be clipped) +@property CGSize size; + +/// The insets for constrained size. The inset value should not be negative. Default is UIEdgeInsetsZero. +@property UIEdgeInsets insets; + +/// Custom constrained path. Set this property to ignore `size` and `insets`. Default is nil. +@property (nullable, copy) UIBezierPath *path; + +/// An array of `UIBezierPath` for path exclusion. Default is nil. +@property (nullable, copy) NSArray *exclusionPaths; + +/// Path line width. Default is 0; +@property CGFloat pathLineWidth; + +/// YES:(PathFillEvenOdd) Text is filled in the area that would be painted if the path were given to CGContextEOFillPath. +/// NO: (PathFillWindingNumber) Text is fill in the area that would be painted if the path were given to CGContextFillPath. +/// Default is YES; +@property (getter=isPathFillEvenOdd) BOOL pathFillEvenOdd; + +/// Whether the text is vertical form (may used for CJK text layout). Default is NO. +@property (getter=isVerticalForm) BOOL verticalForm; + +/// Maximum number of rows, 0 means no limit. Default is 0. +@property NSUInteger maximumNumberOfRows; + +/// The line truncation type, default is none. +@property YYTextTruncationType truncationType; + +/// The truncation token. If nil, the layout will use "…" instead. Default is nil. +@property (nullable, copy) NSAttributedString *truncationToken; + +/// This modifier is applied to the lines before the layout is completed, +/// give you a chance to modify the line position. Default is nil. +@property (nullable, copy) id linePositionModifier; +@end + + +/** + The YYTextLinePositionModifier protocol declares the required method to modify + the line position in text layout progress. See `YYTextLinePositionSimpleModifier` for example. + */ +@protocol YYTextLinePositionModifier +@required +/** + This method will called before layout is completed. The method should be thread-safe. + @param lines An array of YYTextLine. + @param text The full text. + @param container The layout container. + */ +- (void)modifyLines:(NSArray *)lines fromText:(NSAttributedString *)text inContainer:(YYTextContainer *)container; +@end + + +/** + A simple implementation of `YYTextLinePositionModifier`. It can fix each line's position + to a specified value, lets each line of height be the same. + */ +@interface YYTextLinePositionSimpleModifier : NSObject +@property (assign) CGFloat fixedLineHeight; ///< The fixed line height (distance between two baseline). +@end + + + +/** + YYTextLayout class is a readonly class stores text layout result. + All the property in this class is readonly, and should not be changed. + The methods in this class is thread-safe (except some of the draw methods). + + example: (layout with a circle exclusion path) + + ┌──────────────────────────┐ <------ container + │ [--------Line0--------] │ <- Row0 + │ [--------Line1--------] │ <- Row1 + │ [-Line2-] [-Line3-] │ <- Row2 + │ [-Line4] [Line5-] │ <- Row3 + │ [-Line6-] [-Line7-] │ <- Row4 + │ [--------Line8--------] │ <- Row5 + │ [--------Line9--------] │ <- Row6 + └──────────────────────────┘ + */ +@interface YYTextLayout : NSObject + + +#pragma mark - Generate text layout +///============================================================================= +/// @name Generate text layout +///============================================================================= + +/** + Generate a layout with the given container size and text. + + @param size The text container's size + @param text The text (if nil, returns nil). + @return A new layout, or nil when an error occurs. +*/ ++ (nullable YYTextLayout *)layoutWithContainerSize:(CGSize)size text:(NSAttributedString *)text; + +/** + Generate a layout with the given container and text. + + @param container The text container (if nil, returns nil). + @param text The text (if nil, returns nil). + @return A new layout, or nil when an error occurs. + */ ++ (nullable YYTextLayout *)layoutWithContainer:(YYTextContainer *)container text:(NSAttributedString *)text; + +/** + Generate a layout with the given container and text. + + @param container The text container (if nil, returns nil). + @param text The text (if nil, returns nil). + @param range The text range (if out of range, returns nil). If the + length of the range is 0, it means the length is no limit. + @return A new layout, or nil when an error occurs. + */ ++ (nullable YYTextLayout *)layoutWithContainer:(YYTextContainer *)container text:(NSAttributedString *)text range:(NSRange)range; + +/** + Generate layouts with the given containers and text. + + @param containers An array of YYTextContainer object (if nil, returns nil). + @param text The text (if nil, returns nil). + @return An array of YYTextLayout object (the count is same as containers), + or nil when an error occurs. + */ ++ (nullable NSArray *)layoutWithContainers:(NSArray *)containers + text:(NSAttributedString *)text; + +/** + Generate layouts with the given containers and text. + + @param containers An array of YYTextContainer object (if nil, returns nil). + @param text The text (if nil, returns nil). + @param range The text range (if out of range, returns nil). If the + length of the range is 0, it means the length is no limit. + @return An array of YYTextLayout object (the count is same as containers), + or nil when an error occurs. + */ ++ (nullable NSArray *)layoutWithContainers:(NSArray *)containers + text:(NSAttributedString *)text + range:(NSRange)range; + +- (instancetype)init UNAVAILABLE_ATTRIBUTE; ++ (instancetype)new UNAVAILABLE_ATTRIBUTE; + + +#pragma mark - Text layout attributes +///============================================================================= +/// @name Text layout attributes +///============================================================================= + +///< The text container +@property (nonatomic, strong, readonly) YYTextContainer *container; +///< The full text +@property (nonatomic, strong, readonly) NSAttributedString *text; +///< The text range in full text +@property (nonatomic, readonly) NSRange range; +///< CTFrameSetter +@property (nonatomic, readonly) CTFramesetterRef frameSetter; +///< CTFrame +@property (nonatomic, readonly) CTFrameRef frame; +///< Array of `YYTextLine`, no truncated +@property (nonatomic, strong, readonly) NSArray *lines; +///< YYTextLine with truncated token, or nil +@property (nullable, nonatomic, strong, readonly) YYTextLine *truncatedLine; +///< Array of `YYTextAttachment` +@property (nullable, nonatomic, strong, readonly) NSArray *attachments; +///< Array of NSRange(wrapped by NSValue) in text +@property (nullable, nonatomic, strong, readonly) NSArray *attachmentRanges; +///< Array of CGRect(wrapped by NSValue) in container +@property (nullable, nonatomic, strong, readonly) NSArray *attachmentRects; +///< Set of Attachment (UIImage/UIView/CALayer) +@property (nullable, nonatomic, strong, readonly) NSSet *attachmentContentsSet; +///< Number of rows +@property (nonatomic, readonly) NSUInteger rowCount; +///< Visible text range +@property (nonatomic, readonly) NSRange visibleRange; +///< Bounding rect (glyphs) +@property (nonatomic, readonly) CGRect textBoundingRect; +///< Bounding size (glyphs and insets, ceil to pixel) +@property (nonatomic, readonly) CGSize textBoundingSize; +///< Has highlight attribute +@property (nonatomic, readonly) BOOL containsHighlight; +///< Has block border attribute +@property (nonatomic, readonly) BOOL needDrawBlockBorder; +///< Has background border attribute +@property (nonatomic, readonly) BOOL needDrawBackgroundBorder; +///< Has shadow attribute +@property (nonatomic, readonly) BOOL needDrawShadow; +///< Has underline attribute +@property (nonatomic, readonly) BOOL needDrawUnderline; +///< Has visible text +@property (nonatomic, readonly) BOOL needDrawText; +///< Has attachment attribute +@property (nonatomic, readonly) BOOL needDrawAttachment; +///< Has inner shadow attribute +@property (nonatomic, readonly) BOOL needDrawInnerShadow; +///< Has strickthrough attribute +@property (nonatomic, readonly) BOOL needDrawStrikethrough; +///< Has border attribute +@property (nonatomic, readonly) BOOL needDrawBorder; + + +#pragma mark - Query information from text layout +///============================================================================= +/// @name Query information from text layout +///============================================================================= + +/** + The first line index for row. + + @param row A row index. + @return The line index, or NSNotFound if not found. + */ +- (NSUInteger)lineIndexForRow:(NSUInteger)row; + +/** + The number of lines for row. + + @param row A row index. + @return The number of lines, or NSNotFound when an error occurs. + */ +- (NSUInteger)lineCountForRow:(NSUInteger)row; + +/** + The row index for line. + + @param line A row index. + + @return The row index, or NSNotFound if not found. + */ +- (NSUInteger)rowIndexForLine:(NSUInteger)line; + +/** + The line index for a specified point. + + @discussion It returns NSNotFound if there's no text at the point. + + @param point A point in the container. + @return The line index, or NSNotFound if not found. + */ +- (NSUInteger)lineIndexForPoint:(CGPoint)point; + +/** + The line index closest to a specified point. + + @param point A point in the container. + @return The line index, or NSNotFound if no line exist in layout. + */ +- (NSUInteger)closestLineIndexForPoint:(CGPoint)point; + +/** + The offset in container for a text position in a specified line. + + @discussion The offset is the text position's baseline point.x. + If the container is vertical form, the offset is the baseline point.y; + + @param position The text position in string. + @param lineIndex The line index. + @return The offset in container, or CGFLOAT_MAX if not found. + */ +- (CGFloat)offsetForTextPosition:(NSUInteger)position lineIndex:(NSUInteger)lineIndex; + +/** + The text position for a point in a specified line. + + @discussion This method just call CTLineGetStringIndexForPosition() and does + NOT consider the emoji, line break character, binding text... + + @param point A point in the container. + @param lineIndex The line index. + @return The text position, or NSNotFound if not found. + */ +- (NSUInteger)textPositionForPoint:(CGPoint)point lineIndex:(NSUInteger)lineIndex; + +/** + The closest text position to a specified point. + + @discussion This method takes into account the restrict of emoji, line break + character, binding text and text affinity. + + @param point A point in the container. + @return A text position, or nil if not found. + */ +- (nullable YYTextPosition *)closestPositionToPoint:(CGPoint)point; + +/** + Returns the new position when moving selection grabber in text view. + + @discussion There are two grabber in the text selection period, user can only + move one grabber at the same time. + + @param point A point in the container. + @param oldPosition The old text position for the moving grabber. + @param otherPosition The other position in text selection view. + + @return A text position, or nil if not found. + */ +- (nullable YYTextPosition *)positionForPoint:(CGPoint)point + oldPosition:(YYTextPosition *)oldPosition + otherPosition:(YYTextPosition *)otherPosition; + +/** + Returns the character or range of characters that is at a given point in the container. + If there is no text at the point, returns nil. + + @discussion This method takes into account the restrict of emoji, line break + character, binding text and text affinity. + + @param point A point in the container. + @return An object representing a range that encloses a character (or characters) + at point. Or nil if not found. + */ +- (nullable YYTextRange *)textRangeAtPoint:(CGPoint)point; + +/** + Returns the closest character or range of characters that is at a given point in + the container. + + @discussion This method takes into account the restrict of emoji, line break + character, binding text and text affinity. + + @param point A point in the container. + @return An object representing a range that encloses a character (or characters) + at point. Or nil if not found. + */ +- (nullable YYTextRange *)closestTextRangeAtPoint:(CGPoint)point; + +/** + If the position is inside an emoji, composed character sequences, line break '\\r\\n' + or custom binding range, then returns the range by extend the position. Otherwise, + returns a zero length range from the position. + + @param position A text-position object that identifies a location in layout. + + @return A text-range object that extend the position. Or nil if an error occurs + */ +- (nullable YYTextRange *)textRangeByExtendingPosition:(YYTextPosition *)position; + +/** + Returns a text range at a given offset in a specified direction from another + text position to its farthest extent in a certain direction of layout. + + @param position A text-position object that identifies a location in layout. + @param direction A constant that indicates a direction of layout (right, left, up, down). + @param offset A character offset from position. + + @return A text-range object that represents the distance from position to the + farthest extent in direction. Or nil if an error occurs. + */ +- (nullable YYTextRange *)textRangeByExtendingPosition:(YYTextPosition *)position + inDirection:(UITextLayoutDirection)direction + offset:(NSInteger)offset; + +/** + Returns the line index for a given text position. + + @discussion This method takes into account the text affinity. + + @param position A text-position object that identifies a location in layout. + @return The line index, or NSNotFound if not found. + */ +- (NSUInteger)lineIndexForPosition:(YYTextPosition *)position; + +/** + Returns the baseline position for a given text position. + + @param position An object that identifies a location in the layout. + @return The baseline position for text, or CGPointZero if not found. + */ +- (CGPoint)linePositionForPosition:(YYTextPosition *)position; + +/** + Returns a rectangle used to draw the caret at a given insertion point. + + @param position An object that identifies a location in the layout. + @return A rectangle that defines the area for drawing the caret. The width is + always zero in normal container, the height is always zero in vertical form container. + If not found, it returns CGRectNull. + */ +- (CGRect)caretRectForPosition:(YYTextPosition *)position; + +/** + Returns the first rectangle that encloses a range of text in the layout. + + @param range An object that represents a range of text in layout. + + @return The first rectangle in a range of text. You might use this rectangle to + draw a correction rectangle. The "first" in the name refers the rectangle + enclosing the first line when the range encompasses multiple lines of text. + If not found, it returns CGRectNull. + */ +- (CGRect)firstRectForRange:(YYTextRange *)range; + +/** + Returns the rectangle union that encloses a range of text in the layout. + + @param range An object that represents a range of text in layout. + + @return A rectangle that defines the area than encloses the range. + If not found, it returns CGRectNull. + */ +- (CGRect)rectForRange:(YYTextRange *)range; + +/** + Returns an array of selection rects corresponding to the range of text. + The start and end rect can be used to show grabber. + + @param range An object representing a range in text. + @return An array of `YYTextSelectionRect` objects that encompass the selection. + If not found, the array is empty. + */ +- (NSArray *)selectionRectsForRange:(YYTextRange *)range; + +/** + Returns an array of selection rects corresponding to the range of text. + + @param range An object representing a range in text. + @return An array of `YYTextSelectionRect` objects that encompass the selection. + If not found, the array is empty. + */ +- (NSArray *)selectionRectsWithoutStartAndEndForRange:(YYTextRange *)range; + +/** + Returns the start and end selection rects corresponding to the range of text. + The start and end rect can be used to show grabber. + + @param range An object representing a range in text. + @return An array of `YYTextSelectionRect` objects contains the start and end to + the selection. If not found, the array is empty. + */ +- (NSArray *)selectionRectsWithOnlyStartAndEndForRange:(YYTextRange *)range; + + +#pragma mark - Draw text layout +///============================================================================= +/// @name Draw text layout +///============================================================================= + +/** + Draw the layout and show the attachments. + + @discussion If the `view` parameter is not nil, then the attachment views will + add to this `view`, and if the `layer` parameter is not nil, then the attachment + layers will add to this `layer`. + + @warning This method should be called on main thread if `view` or `layer` parameter + is not nil and there's UIView or CALayer attachments in layout. + Otherwise, it can be called on any thread. + + @param context The draw context. Pass nil to avoid text and image drawing. + @param size The context size. + @param point The point at which to draw the layout. + @param view The attachment views will add to this view. + @param layer The attachment layers will add to this layer. + @param debug The debug option. Pass nil to avoid debug drawing. + @param cancel The cancel checker block. It will be called in drawing progress. + If it returns YES, the further draw progress will be canceled. + Pass nil to ignore this feature. + */ +- (void)drawInContext:(nullable CGContextRef)context + size:(CGSize)size + point:(CGPoint)point + view:(nullable UIView *)view + layer:(nullable CALayer *)layer + debug:(nullable YYTextDebugOption *)debug + cancel:(nullable BOOL (^)(void))cancel; + +/** + Draw the layout text and image (without view or layer attachments). + + @discussion This method is thread safe and can be called on any thread. + + @param context The draw context. Pass nil to avoid text and image drawing. + @param size The context size. + @param debug The debug option. Pass nil to avoid debug drawing. + */ +- (void)drawInContext:(nullable CGContextRef)context + size:(CGSize)size + debug:(nullable YYTextDebugOption *)debug; + +/** + Show view and layer attachments. + + @warning This method must be called on main thread. + + @param view The attachment views will add to this view. + @param layer The attachment layers will add to this layer. + */ +- (void)addAttachmentToView:(nullable UIView *)view layer:(nullable CALayer *)layer; + +/** + Remove attachment views and layers from their super container. + + @warning This method must be called on main thread. + */ +- (void)removeAttachmentFromViewAndLayer; + +@end + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/YYTextLine.h b/YYText/include/YYTextLine.h new file mode 100644 index 00000000..3151ebca --- /dev/null +++ b/YYText/include/YYTextLine.h @@ -0,0 +1,84 @@ +// +// YYTextLine.h +// YYText +// +// Created by ibireme on 15/3/10. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import +#import + +#if __has_include() +#import +#else +#import "YYTextAttribute.h" +#endif + +@class YYTextRunGlyphRange; + +NS_ASSUME_NONNULL_BEGIN + +/** + A text line object wrapped `CTLineRef`, see `YYTextLayout` for more. + */ +@interface YYTextLine : NSObject + ++ (instancetype)lineWithCTLine:(CTLineRef)CTLine position:(CGPoint)position vertical:(BOOL)isVertical; + +@property (nonatomic) NSUInteger index; ///< line index +@property (nonatomic) NSUInteger row; ///< line row +@property (nullable, nonatomic, strong) NSArray *> *verticalRotateRange; ///< Run rotate range + +@property (nonatomic, readonly) CTLineRef CTLine; ///< CoreText line +@property (nonatomic, readonly) NSRange range; ///< string range +@property (nonatomic, readonly) BOOL vertical; ///< vertical form + +@property (nonatomic, readonly) CGRect bounds; ///< bounds (ascent + descent) +@property (nonatomic, readonly) CGSize size; ///< bounds.size +@property (nonatomic, readonly) CGFloat width; ///< bounds.size.width +@property (nonatomic, readonly) CGFloat height; ///< bounds.size.height +@property (nonatomic, readonly) CGFloat top; ///< bounds.origin.y +@property (nonatomic, readonly) CGFloat bottom; ///< bounds.origin.y + bounds.size.height +@property (nonatomic, readonly) CGFloat left; ///< bounds.origin.x +@property (nonatomic, readonly) CGFloat right; ///< bounds.origin.x + bounds.size.width + +@property (nonatomic) CGPoint position; ///< baseline position +@property (nonatomic, readonly) CGFloat ascent; ///< line ascent +@property (nonatomic, readonly) CGFloat descent; ///< line descent +@property (nonatomic, readonly) CGFloat leading; ///< line leading +@property (nonatomic, readonly) CGFloat lineWidth; ///< line width +@property (nonatomic, readonly) CGFloat trailingWhitespaceWidth; + +@property (nullable, nonatomic, readonly) NSArray *attachments; ///< YYTextAttachment +@property (nullable, nonatomic, readonly) NSArray *attachmentRanges; ///< NSRange(NSValue) +@property (nullable, nonatomic, readonly) NSArray *attachmentRects; ///< CGRect(NSValue) + +@end + + +typedef NS_ENUM(NSUInteger, YYTextRunGlyphDrawMode) { + /// No rotate. + YYTextRunGlyphDrawModeHorizontal = 0, + + /// Rotate vertical for single glyph. + YYTextRunGlyphDrawModeVerticalRotate = 1, + + /// Rotate vertical for single glyph, and move the glyph to a better position, + /// such as fullwidth punctuation. + YYTextRunGlyphDrawModeVerticalRotateMove = 2, +}; + +/** + A range in CTRun, used for vertical form. + */ +@interface YYTextRunGlyphRange : NSObject +@property (nonatomic) NSRange glyphRangeInRun; +@property (nonatomic) YYTextRunGlyphDrawMode drawMode; ++ (instancetype)rangeWithRange:(NSRange)range drawMode:(YYTextRunGlyphDrawMode)mode; +@end + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/YYTextMagnifier.h b/YYText/include/YYTextMagnifier.h new file mode 100644 index 00000000..adf131b1 --- /dev/null +++ b/YYText/include/YYTextMagnifier.h @@ -0,0 +1,52 @@ +// +// YYTextMagnifier.h +// YYText +// +// Created by ibireme on 15/2/25. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +#if __has_include() +#import +#else +#import "YYTextAttribute.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + +/// Magnifier type +typedef NS_ENUM(NSInteger, YYTextMagnifierType) { + YYTextMagnifierTypeCaret, ///< Circular magnifier + YYTextMagnifierTypeRanged, ///< Round rectangle magnifier +}; + +/** + A magnifier view which can be displayed in `YYTextEffectWindow`. + + @discussion Use `magnifierWithType:` to create instance. + Typically, you should not use this class directly. + */ +@interface YYTextMagnifier : UIView + +/// Create a mangifier with the specified type. @param type The magnifier type. ++ (id)magnifierWithType:(YYTextMagnifierType)type; + +@property (nonatomic, readonly) YYTextMagnifierType type; ///< Type of magnifier +@property (nonatomic, readonly) CGSize fitSize; ///< The 'best' size for magnifier view. +@property (nonatomic, readonly) CGSize snapshotSize; ///< The 'best' snapshot image size for magnifier. +@property (nullable, nonatomic, strong) UIImage *snapshot; ///< The image in magnifier (readwrite). + +@property (nullable, nonatomic, weak) UIView *hostView; ///< The coordinate based view. +@property (nonatomic) CGPoint hostCaptureCenter; ///< The snapshot capture center in `hostView`. +@property (nonatomic) CGPoint hostPopoverCenter; ///< The popover center in `hostView`. +@property (nonatomic) BOOL hostVerticalForm; ///< The host view is vertical form. +@property (nonatomic) BOOL captureDisabled; ///< A hint for `YYTextEffectWindow` to disable capture. +@property (nonatomic) BOOL captureFadeAnimation; ///< Show fade animation when the snapshot image changed. +@end + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/YYTextParser.h b/YYText/include/YYTextParser.h new file mode 100644 index 00000000..111064ba --- /dev/null +++ b/YYText/include/YYTextParser.h @@ -0,0 +1,91 @@ +// +// YYTextParser.h +// YYText +// +// Created by ibireme on 15/3/6. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + The YYTextParser protocol declares the required method for YYTextView and YYLabel + to modify the text during editing. + + You can implement this protocol to add code highlighting or emoticon replacement for + YYTextView and YYLabel. See `YYTextSimpleMarkdownParser` and `YYTextSimpleEmoticonParser` for example. + */ +@protocol YYTextParser +@required +/** + When text is changed in YYTextView or YYLabel, this method will be called. + + @param text The original attributed string. This method may parse the text and + change the text attributes or content. + + @param selectedRange Current selected range in `text`. + This method should correct the range if the text content is changed. If there's + no selected range (such as YYLabel), this value is NULL. + + @return If the 'text' is modified in this method, returns `YES`, otherwise returns `NO`. + */ +- (BOOL)parseText:(nullable NSMutableAttributedString *)text selectedRange:(nullable NSRangePointer)selectedRange; +@end + + + +/** + A simple markdown parser. + + It'a very simple markdown parser, you can use this parser to highlight some + small piece of markdown text. + + This markdown parser use regular expression to parse text, slow and weak. + If you want to write a better parser, try these projests: + https://github.com/NimbusKit/markdown + https://github.com/dreamwieber/AttributedMarkdown + https://github.com/indragiek/CocoaMarkdown + + Or you can use lex/yacc to generate your custom parser. + */ +@interface YYTextSimpleMarkdownParser : NSObject +@property (nonatomic) CGFloat fontSize; ///< default is 14 +@property (nonatomic) CGFloat headerFontSize; ///< default is 20 + +@property (nullable, nonatomic, strong) UIColor *textColor; +@property (nullable, nonatomic, strong) UIColor *controlTextColor; +@property (nullable, nonatomic, strong) UIColor *headerTextColor; +@property (nullable, nonatomic, strong) UIColor *inlineTextColor; +@property (nullable, nonatomic, strong) UIColor *codeTextColor; +@property (nullable, nonatomic, strong) UIColor *linkTextColor; + +- (void)setColorWithBrightTheme; ///< reset the color properties to pre-defined value. +- (void)setColorWithDarkTheme; ///< reset the color properties to pre-defined value. +@end + + + +/** + A simple emoticon parser. + + Use this parser to map some specified piece of string to image emoticon. + Example: "Hello :smile:" -> "Hello 😀" + + It can also be used to extend the "unicode emoticon". + */ +@interface YYTextSimpleEmoticonParser : NSObject + +/** + The custom emoticon mapper. + The key is a specified plain string, such as @":smile:". + The value is a UIImage which will replace the specified plain string in text. + */ +@property (nullable, copy) NSDictionary *emoticonMapper; +@end + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/YYTextRubyAnnotation.h b/YYText/include/YYTextRubyAnnotation.h new file mode 100644 index 00000000..1a138433 --- /dev/null +++ b/YYText/include/YYTextRubyAnnotation.h @@ -0,0 +1,78 @@ +// +// YYTextRubyAnnotation.h +// YYText +// +// Created by ibireme on 15/4/24. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Wrapper for CTRubyAnnotationRef. + + Example: + + YYTextRubyAnnotation *ruby = [YYTextRubyAnnotation new]; + ruby.textBefore = @"zhù yīn"; + CTRubyAnnotationRef ctRuby = ruby.CTRubyAnnotation; + if (ctRuby) { + /// add to attributed string + CFRelease(ctRuby); + } + + */ +@interface YYTextRubyAnnotation : NSObject + +/// Specifies how the ruby text and the base text should be aligned relative to each other. +@property (nonatomic) CTRubyAlignment alignment; + +/// Specifies how the ruby text can overhang adjacent characters. +@property (nonatomic) CTRubyOverhang overhang; + +/// Specifies the size of the annotation text as a percent of the size of the base text. +@property (nonatomic) CGFloat sizeFactor; + + +/// The ruby text is positioned before the base text; +/// i.e. above horizontal text and to the right of vertical text. +@property (nullable, nonatomic, copy) NSString *textBefore; + +/// The ruby text is positioned after the base text; +/// i.e. below horizontal text and to the left of vertical text. +@property (nullable, nonatomic, copy) NSString *textAfter; + +/// The ruby text is positioned to the right of the base text whether it is horizontal or vertical. +/// This is the way that Bopomofo annotations are attached to Chinese text in Taiwan. +@property (nullable, nonatomic, copy) NSString *textInterCharacter; + +/// The ruby text follows the base text with no special styling. +@property (nullable, nonatomic, copy) NSString *textInline; + + +/** + Create a ruby object from CTRuby object. + + @param ctRuby A CTRuby object. + + @return A ruby object, or nil when an error occurs. + */ ++ (instancetype)rubyWithCTRubyRef:(CTRubyAnnotationRef)ctRuby NS_AVAILABLE_IOS(8_0); + +/** + Create a CTRuby object from the instance. + + @return A new CTRuby object, or NULL when an error occurs. + The returned value should be release after used. + */ +- (nullable CTRubyAnnotationRef)CTRubyAnnotation CF_RETURNS_RETAINED NS_AVAILABLE_IOS(8_0); + +@end + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/YYTextRunDelegate.h b/YYText/include/YYTextRunDelegate.h new file mode 100644 index 00000000..ac73b065 --- /dev/null +++ b/YYText/include/YYTextRunDelegate.h @@ -0,0 +1,68 @@ +// +// YYTextRunDelegate.h +// YYText +// +// Created by ibireme on 14/10/14. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + Wrapper for CTRunDelegateRef. + + Example: + + YYTextRunDelegate *delegate = [YYTextRunDelegate new]; + delegate.ascent = 20; + delegate.descent = 4; + delegate.width = 20; + CTRunDelegateRef ctRunDelegate = delegate.CTRunDelegate; + if (ctRunDelegate) { + /// add to attributed string + CFRelease(ctRunDelegate); + } + + */ +@interface YYTextRunDelegate : NSObject + +/** + Creates and returns the CTRunDelegate. + + @discussion You need call CFRelease() after used. + The CTRunDelegateRef has a strong reference to this YYTextRunDelegate object. + In CoreText, use CTRunDelegateGetRefCon() to get this YYTextRunDelegate object. + + @return The CTRunDelegate object. + */ +- (nullable CTRunDelegateRef)CTRunDelegate CF_RETURNS_RETAINED; + +/** + Additional information about the the run delegate. + */ +@property (nullable, nonatomic, strong) NSDictionary *userInfo; + +/** + The typographic ascent of glyphs in the run. + */ +@property (nonatomic) CGFloat ascent; + +/** + The typographic descent of glyphs in the run. + */ +@property (nonatomic) CGFloat descent; + +/** + The typographic width of glyphs in the run. + */ +@property (nonatomic) CGFloat width; + +@end + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/YYTextSelectionView.h b/YYText/include/YYTextSelectionView.h new file mode 100644 index 00000000..be5f38ee --- /dev/null +++ b/YYText/include/YYTextSelectionView.h @@ -0,0 +1,78 @@ +// +// YYTextSelectionView.h +// YYText +// +// Created by ibireme on 15/2/25. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +#if __has_include() +#import +#import +#else +#import "YYTextAttribute.h" +#import "YYTextInput.h" +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + A single dot view. The frame should be foursquare. + Change the background color for display. + + @discussion Typically, you should not use this class directly. + */ +@interface YYSelectionGrabberDot : UIView +/// Dont't access this property. It was used by `YYTextEffectWindow`. +@property (nonatomic, strong) UIView *mirror; +@end + + +/** + A grabber (stick with a dot). + + @discussion Typically, you should not use this class directly. + */ +@interface YYSelectionGrabber : UIView + +@property (nonatomic, readonly) YYSelectionGrabberDot *dot; ///< the dot view +@property (nonatomic) YYTextDirection dotDirection; ///< don't support composite direction +@property (nullable, nonatomic, strong) UIColor *color; ///< tint color, default is nil + +@end + + +/** + The selection view for text edit and select. + + @discussion Typically, you should not use this class directly. + */ +@interface YYTextSelectionView : UIView + +@property (nullable, nonatomic, weak) UIView *hostView; ///< the holder view +@property (nullable, nonatomic, strong) UIColor *color; ///< the tint color +@property (nonatomic, getter = isCaretBlinks) BOOL caretBlinks; ///< whether the caret is blinks +@property (nonatomic, getter = isCaretVisible) BOOL caretVisible; ///< whether the caret is visible +@property (nonatomic, getter = isVerticalForm) BOOL verticalForm; ///< weather the text view is vertical form + +@property (nonatomic) CGRect caretRect; ///< caret rect (width==0 or height==0) +@property (nullable, nonatomic, copy) NSArray *selectionRects; ///< default is nil + +@property (nonatomic, readonly) UIView *caretView; +@property (nonatomic, readonly) YYSelectionGrabber *startGrabber; +@property (nonatomic, readonly) YYSelectionGrabber *endGrabber; + +- (BOOL)isGrabberContainsPoint:(CGPoint)point; +- (BOOL)isStartGrabberContainsPoint:(CGPoint)point; +- (BOOL)isEndGrabberContainsPoint:(CGPoint)point; +- (BOOL)isCaretContainsPoint:(CGPoint)point; +- (BOOL)isSelectionRectsContainsPoint:(CGPoint)point; + +@end + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/YYTextTransaction.h b/YYText/include/YYTextTransaction.h new file mode 100644 index 00000000..22124f71 --- /dev/null +++ b/YYText/include/YYTextTransaction.h @@ -0,0 +1,42 @@ +// +// YYTextTransaction.h +// YYText +// +// Created by ibireme on 15/4/18. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + YYTextTransaction let you perform a selector once before current runloop sleep. + */ +@interface YYTextTransaction : NSObject + +/** + Creates and returns a transaction with a specified target and selector. + + @param target A specified target, the target is retained until runloop end. + @param selector A selector for target. + + @return A new transaction, or nil if an error occurs. + */ ++ (YYTextTransaction *)transactionWithTarget:(id)target selector:(SEL)selector; + +/** + Commit the trancaction to main runloop. + + @discussion It will perform the selector on the target once before main runloop's + current loop sleep. If the same transaction (same target and same selector) has + already commit to runloop in this loop, this method do nothing. + */ +- (void)commit; + +@end + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/YYTextUtilities.h b/YYText/include/YYTextUtilities.h new file mode 100644 index 00000000..98c63825 --- /dev/null +++ b/YYText/include/YYTextUtilities.h @@ -0,0 +1,563 @@ +// +// YYTextUtilities.h +// YYText +// +// Created by ibireme on 15/4/6. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import +#import +#import + + +#ifndef YYTEXT_CLAMP // return the clamped value +#define YYTEXT_CLAMP(_x_, _low_, _high_) (((_x_) > (_high_)) ? (_high_) : (((_x_) < (_low_)) ? (_low_) : (_x_))) +#endif + +#ifndef YYTEXT_SWAP // swap two value +#define YYTEXT_SWAP(_a_, _b_) do { __typeof__(_a_) _tmp_ = (_a_); (_a_) = (_b_); (_b_) = _tmp_; } while (0) +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + Whether the character is 'line break char': + U+000D (\\r or CR) + U+2028 (Unicode line separator) + U+000A (\\n or LF) + U+2029 (Unicode paragraph separator) + + @param c A character + @return YES or NO. + */ +static inline BOOL YYTextIsLinebreakChar(unichar c) { + switch (c) { + case 0x000D: + case 0x2028: + case 0x000A: + case 0x2029: + return YES; + default: + return NO; + } +} + +/** + Whether the string is a 'line break': + U+000D (\\r or CR) + U+2028 (Unicode line separator) + U+000A (\\n or LF) + U+2029 (Unicode paragraph separator) + \\r\\n, in that order (also known as CRLF) + + @param str A string + @return YES or NO. + */ +static inline BOOL YYTextIsLinebreakString(NSString * _Nullable str) { + if (str.length > 2 || str.length == 0) return NO; + if (str.length == 1) { + unichar c = [str characterAtIndex:0]; + return YYTextIsLinebreakChar(c); + } else { + return ([str characterAtIndex:0] == '\r') && ([str characterAtIndex:1] == '\n'); + } +} + +/** + If the string has a 'line break' suffix, return the 'line break' length. + + @param str A string. + @return The length of the tail line break: 0, 1 or 2. + */ +static inline NSUInteger YYTextLinebreakTailLength(NSString * _Nullable str) { + if (str.length >= 2) { + unichar c2 = [str characterAtIndex:str.length - 1]; + if (YYTextIsLinebreakChar(c2)) { + unichar c1 = [str characterAtIndex:str.length - 2]; + if (c1 == '\r' && c2 == '\n') return 2; + else return 1; + } else { + return 0; + } + } else if (str.length == 1) { + return YYTextIsLinebreakChar([str characterAtIndex:0]) ? 1 : 0; + } else { + return 0; + } +} + +/** + Convert `UIDataDetectorTypes` to `NSTextCheckingType`. + + @param types The `UIDataDetectorTypes` type. + @return The `NSTextCheckingType` type. + */ +static inline NSTextCheckingType YYTextNSTextCheckingTypeFromUIDataDetectorType(UIDataDetectorTypes types) { + NSTextCheckingType t = 0; + if (types & UIDataDetectorTypePhoneNumber) t |= NSTextCheckingTypePhoneNumber; + if (types & UIDataDetectorTypeLink) t |= NSTextCheckingTypeLink; + if (types & UIDataDetectorTypeAddress) t |= NSTextCheckingTypeAddress; + if (types & UIDataDetectorTypeCalendarEvent) t |= NSTextCheckingTypeDate; + return t; +} + +/** + Whether the font is `AppleColorEmoji` font. + + @param font A font. + @return YES: the font is Emoji, NO: the font is not Emoji. + */ +static inline BOOL YYTextUIFontIsEmoji(UIFont *font) { + return [font.fontName isEqualToString:@"AppleColorEmoji"]; +} + +/** + Whether the font is `AppleColorEmoji` font. + + @param font A font. + @return YES: the font is Emoji, NO: the font is not Emoji. + */ +static inline BOOL YYTextCTFontIsEmoji(CTFontRef font) { + BOOL isEmoji = NO; + CFStringRef name = CTFontCopyPostScriptName(font); + if (name && CFEqual(CFSTR("AppleColorEmoji"), name)) isEmoji = YES; + if (name) CFRelease(name); + return isEmoji; +} + +/** + Whether the font is `AppleColorEmoji` font. + + @param font A font. + @return YES: the font is Emoji, NO: the font is not Emoji. + */ +static inline BOOL YYTextCGFontIsEmoji(CGFontRef font) { + BOOL isEmoji = NO; + CFStringRef name = CGFontCopyPostScriptName(font); + if (name && CFEqual(CFSTR("AppleColorEmoji"), name)) isEmoji = YES; + if (name) CFRelease(name); + return isEmoji; +} + +/** + Whether the font contains color bitmap glyphs. + + @discussion Only `AppleColorEmoji` contains color bitmap glyphs in iOS system fonts. + @param font A font. + @return YES: the font contains color bitmap glyphs, NO: the font has no color bitmap glyph. + */ +static inline BOOL YYTextCTFontContainsColorBitmapGlyphs(CTFontRef font) { + return (CTFontGetSymbolicTraits(font) & kCTFontTraitColorGlyphs) != 0; +} + +/** + Whether the glyph is bitmap. + + @param font The glyph's font. + @param glyph The glyph which is created from the specified font. + @return YES: the glyph is bitmap, NO: the glyph is vector. + */ +static inline BOOL YYTextCGGlyphIsBitmap(CTFontRef font, CGGlyph glyph) { + if (!font && !glyph) return NO; + if (!YYTextCTFontContainsColorBitmapGlyphs(font)) return NO; + CGPathRef path = CTFontCreatePathForGlyph(font, glyph, NULL); + if (path) { + CFRelease(path); + return NO; + } + return YES; +} + +/** + Get the `AppleColorEmoji` font's ascent with a specified font size. + It may used to create custom emoji. + + @param fontSize The specified font size. + @return The font ascent. + */ +static inline CGFloat YYTextEmojiGetAscentWithFontSize(CGFloat fontSize) { + if (fontSize < 16) { + return 1.25 * fontSize; + } else if (16 <= fontSize && fontSize <= 24) { + return 0.5 * fontSize + 12; + } else { + return fontSize; + } +} + +/** + Get the `AppleColorEmoji` font's descent with a specified font size. + It may used to create custom emoji. + + @param fontSize The specified font size. + @return The font descent. + */ +static inline CGFloat YYTextEmojiGetDescentWithFontSize(CGFloat fontSize) { + if (fontSize < 16) { + return 0.390625 * fontSize; + } else if (16 <= fontSize && fontSize <= 24) { + return 0.15625 * fontSize + 3.75; + } else { + return 0.3125 * fontSize; + } + return 0; +} + +/** + Get the `AppleColorEmoji` font's glyph bounding rect with a specified font size. + It may used to create custom emoji. + + @param fontSize The specified font size. + @return The font glyph bounding rect. + */ +static inline CGRect YYTextEmojiGetGlyphBoundingRectWithFontSize(CGFloat fontSize) { + CGRect rect; + rect.origin.x = 0.75; + rect.size.width = rect.size.height = YYTextEmojiGetAscentWithFontSize(fontSize); + if (fontSize < 16) { + rect.origin.y = -0.2525 * fontSize; + } else if (16 <= fontSize && fontSize <= 24) { + rect.origin.y = 0.1225 * fontSize -6; + } else { + rect.origin.y = -0.1275 * fontSize; + } + return rect; +} + + +/** + Get the character set which should rotate in vertical form. + @return The shared character set. + */ +NSCharacterSet *YYTextVerticalFormRotateCharacterSet(void); + +/** + Get the character set which should rotate and move in vertical form. + @return The shared character set. + */ +NSCharacterSet *YYTextVerticalFormRotateAndMoveCharacterSet(void); + + + +/// Convert degrees to radians. +static inline CGFloat YYTextDegreesToRadians(CGFloat degrees) { + return degrees * M_PI / 180; +} + +/// Convert radians to degrees. +static inline CGFloat YYTextRadiansToDegrees(CGFloat radians) { + return radians * 180 / M_PI; +} + + + +/// Get the transform rotation. +/// @return the rotation in radians [-PI,PI] ([-180°,180°]) +static inline CGFloat YYTextCGAffineTransformGetRotation(CGAffineTransform transform) { + return atan2(transform.b, transform.a); +} + +/// Get the transform's scale.x +static inline CGFloat YYTextCGAffineTransformGetScaleX(CGAffineTransform transform) { + return sqrt(transform.a * transform.a + transform.c * transform.c); +} + +/// Get the transform's scale.y +static inline CGFloat YYTextCGAffineTransformGetScaleY(CGAffineTransform transform) { + return sqrt(transform.b * transform.b + transform.d * transform.d); +} + +/// Get the transform's translate.x +static inline CGFloat YYTextCGAffineTransformGetTranslateX(CGAffineTransform transform) { + return transform.tx; +} + +/// Get the transform's translate.y +static inline CGFloat YYTextCGAffineTransformGetTranslateY(CGAffineTransform transform) { + return transform.ty; +} + +/** + If you have 3 pair of points transformed by a same CGAffineTransform: + p1 (transform->) q1 + p2 (transform->) q2 + p3 (transform->) q3 + This method returns the original transform matrix from these 3 pair of points. + + @see http://stackoverflow.com/questions/13291796/calculate-values-for-a-cgaffinetransform-from-three-points-in-each-of-two-uiview + */ +CGAffineTransform YYTextCGAffineTransformGetFromPoints(CGPoint before[_Nullable 3], CGPoint after[_Nullable 3]); + +/// Get the transform which can converts a point from the coordinate system of a given view to another. +CGAffineTransform YYTextCGAffineTransformGetFromViews(UIView *from, UIView *to); + +/// Create a skew transform. +static inline CGAffineTransform YYTextCGAffineTransformMakeSkew(CGFloat x, CGFloat y){ + CGAffineTransform transform = CGAffineTransformIdentity; + transform.c = -x; + transform.b = y; + return transform; +} + +/// Negates/inverts a UIEdgeInsets. +static inline UIEdgeInsets YYTextUIEdgeInsetsInvert(UIEdgeInsets insets) { + return UIEdgeInsetsMake(-insets.top, -insets.left, -insets.bottom, -insets.right); +} + +/// Convert CALayer's gravity string to UIViewContentMode. +UIViewContentMode YYTextCAGravityToUIViewContentMode(NSString *gravity); + +/// Convert UIViewContentMode to CALayer's gravity string. +NSString *YYTextUIViewContentModeToCAGravity(UIViewContentMode contentMode); + + + +/** + Returns a rectangle to fit the `rect` with specified content mode. + + @param rect The constrant rect + @param size The content size + @param mode The content mode + @return A rectangle for the given content mode. + @discussion UIViewContentModeRedraw is same as UIViewContentModeScaleToFill. + */ +CGRect YYTextCGRectFitWithContentMode(CGRect rect, CGSize size, UIViewContentMode mode); + +/// Returns the center for the rectangle. +static inline CGPoint YYTextCGRectGetCenter(CGRect rect) { + return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect)); +} + +/// Returns the area of the rectangle. +static inline CGFloat YYTextCGRectGetArea(CGRect rect) { + if (CGRectIsNull(rect)) return 0; + rect = CGRectStandardize(rect); + return rect.size.width * rect.size.height; +} + +/// Returns the distance between two points. +static inline CGFloat YYTextCGPointGetDistanceToPoint(CGPoint p1, CGPoint p2) { + return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)); +} + +/// Returns the minmium distance between a point to a rectangle. +static inline CGFloat YYTextCGPointGetDistanceToRect(CGPoint p, CGRect r) { + r = CGRectStandardize(r); + if (CGRectContainsPoint(r, p)) return 0; + CGFloat distV, distH; + if (CGRectGetMinY(r) <= p.y && p.y <= CGRectGetMaxY(r)) { + distV = 0; + } else { + distV = p.y < CGRectGetMinY(r) ? CGRectGetMinY(r) - p.y : p.y - CGRectGetMaxY(r); + } + if (CGRectGetMinX(r) <= p.x && p.x <= CGRectGetMaxX(r)) { + distH = 0; + } else { + distH = p.x < CGRectGetMinX(r) ? CGRectGetMinX(r) - p.x : p.x - CGRectGetMaxX(r); + } + return MAX(distV, distH); +} + + +/// Get main screen's scale. +CGFloat YYTextScreenScale(void); + +/// Get main screen's size. Height is always larger than width. +CGSize YYTextScreenSize(void); + +/// Convert point to pixel. +static inline CGFloat YYTextCGFloatToPixel(CGFloat value) { + return value * YYTextScreenScale(); +} + +/// Convert pixel to point. +static inline CGFloat YYTextCGFloatFromPixel(CGFloat value) { + return value / YYTextScreenScale(); +} + +/// floor point value for pixel-aligned +static inline CGFloat YYTextCGFloatPixelFloor(CGFloat value) { + CGFloat scale = YYTextScreenScale(); + return floor(value * scale) / scale; +} + +/// round point value for pixel-aligned +static inline CGFloat YYTextCGFloatPixelRound(CGFloat value) { + CGFloat scale = YYTextScreenScale(); + return round(value * scale) / scale; +} + +/// ceil point value for pixel-aligned +static inline CGFloat YYTextCGFloatPixelCeil(CGFloat value) { + CGFloat scale = YYTextScreenScale(); + return ceil(value * scale) / scale; +} + +/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned) +static inline CGFloat YYTextCGFloatPixelHalf(CGFloat value) { + CGFloat scale = YYTextScreenScale(); + return (floor(value * scale) + 0.5) / scale; +} + +/// floor point value for pixel-aligned +static inline CGPoint YYTextCGPointPixelFloor(CGPoint point) { + CGFloat scale = YYTextScreenScale(); + return CGPointMake(floor(point.x * scale) / scale, + floor(point.y * scale) / scale); +} + +/// round point value for pixel-aligned +static inline CGPoint YYTextCGPointPixelRound(CGPoint point) { + CGFloat scale = YYTextScreenScale(); + return CGPointMake(round(point.x * scale) / scale, + round(point.y * scale) / scale); +} + +/// ceil point value for pixel-aligned +static inline CGPoint YYTextCGPointPixelCeil(CGPoint point) { + CGFloat scale = YYTextScreenScale(); + return CGPointMake(ceil(point.x * scale) / scale, + ceil(point.y * scale) / scale); +} + +/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned) +static inline CGPoint YYTextCGPointPixelHalf(CGPoint point) { + CGFloat scale = YYTextScreenScale(); + return CGPointMake((floor(point.x * scale) + 0.5) / scale, + (floor(point.y * scale) + 0.5) / scale); +} + + + +/// floor point value for pixel-aligned +static inline CGSize YYTextCGSizePixelFloor(CGSize size) { + CGFloat scale = YYTextScreenScale(); + return CGSizeMake(floor(size.width * scale) / scale, + floor(size.height * scale) / scale); +} + +/// round point value for pixel-aligned +static inline CGSize YYTextCGSizePixelRound(CGSize size) { + CGFloat scale = YYTextScreenScale(); + return CGSizeMake(round(size.width * scale) / scale, + round(size.height * scale) / scale); +} + +/// ceil point value for pixel-aligned +static inline CGSize YYTextCGSizePixelCeil(CGSize size) { + CGFloat scale = YYTextScreenScale(); + return CGSizeMake(ceil(size.width * scale) / scale, + ceil(size.height * scale) / scale); +} + +/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned) +static inline CGSize YYTextCGSizePixelHalf(CGSize size) { + CGFloat scale = YYTextScreenScale(); + return CGSizeMake((floor(size.width * scale) + 0.5) / scale, + (floor(size.height * scale) + 0.5) / scale); +} + + + +/// floor point value for pixel-aligned +static inline CGRect YYTextCGRectPixelFloor(CGRect rect) { + CGPoint origin = YYTextCGPointPixelCeil(rect.origin); + CGPoint corner = YYTextCGPointPixelFloor(CGPointMake(rect.origin.x + rect.size.width, + rect.origin.y + rect.size.height)); + CGRect ret = CGRectMake(origin.x, origin.y, corner.x - origin.x, corner.y - origin.y); + if (ret.size.width < 0) ret.size.width = 0; + if (ret.size.height < 0) ret.size.height = 0; + return ret; +} + +/// round point value for pixel-aligned +static inline CGRect YYTextCGRectPixelRound(CGRect rect) { + CGPoint origin = YYTextCGPointPixelRound(rect.origin); + CGPoint corner = YYTextCGPointPixelRound(CGPointMake(rect.origin.x + rect.size.width, + rect.origin.y + rect.size.height)); + return CGRectMake(origin.x, origin.y, corner.x - origin.x, corner.y - origin.y); +} + +/// ceil point value for pixel-aligned +static inline CGRect YYTextCGRectPixelCeil(CGRect rect) { + CGPoint origin = YYTextCGPointPixelFloor(rect.origin); + CGPoint corner = YYTextCGPointPixelCeil(CGPointMake(rect.origin.x + rect.size.width, + rect.origin.y + rect.size.height)); + return CGRectMake(origin.x, origin.y, corner.x - origin.x, corner.y - origin.y); +} + +/// round point value to .5 pixel for path stroke (odd pixel line width pixel-aligned) +static inline CGRect YYTextCGRectPixelHalf(CGRect rect) { + CGPoint origin = YYTextCGPointPixelHalf(rect.origin); + CGPoint corner = YYTextCGPointPixelHalf(CGPointMake(rect.origin.x + rect.size.width, + rect.origin.y + rect.size.height)); + return CGRectMake(origin.x, origin.y, corner.x - origin.x, corner.y - origin.y); +} + + + +/// floor UIEdgeInset for pixel-aligned +static inline UIEdgeInsets YYTextUIEdgeInsetPixelFloor(UIEdgeInsets insets) { + insets.top = YYTextCGFloatPixelFloor(insets.top); + insets.left = YYTextCGFloatPixelFloor(insets.left); + insets.bottom = YYTextCGFloatPixelFloor(insets.bottom); + insets.right = YYTextCGFloatPixelFloor(insets.right); + return insets; +} + +/// ceil UIEdgeInset for pixel-aligned +static inline UIEdgeInsets YYTextUIEdgeInsetPixelCeil(UIEdgeInsets insets) { + insets.top = YYTextCGFloatPixelCeil(insets.top); + insets.left = YYTextCGFloatPixelCeil(insets.left); + insets.bottom = YYTextCGFloatPixelCeil(insets.bottom); + insets.right = YYTextCGFloatPixelCeil(insets.right); + return insets; +} + + + +static inline UIFont * _Nullable YYTextFontWithBold(UIFont *font) { + if (![font respondsToSelector:@selector(fontDescriptor)]) return font; + return [UIFont fontWithDescriptor:[font.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold] size:font.pointSize]; +} + +static inline UIFont * _Nullable YYTextFontWithItalic(UIFont *font) { + if (![font respondsToSelector:@selector(fontDescriptor)]) return font; + return [UIFont fontWithDescriptor:[font.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitItalic] size:font.pointSize]; +} + +static inline UIFont * _Nullable YYTextFontWithBoldItalic(UIFont *font) { + if (![font respondsToSelector:@selector(fontDescriptor)]) return font; + return [UIFont fontWithDescriptor:[font.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold | UIFontDescriptorTraitItalic] size:font.pointSize]; +} + + + +/** + Convert CFRange to NSRange + @param range CFRange @return NSRange + */ +static inline NSRange YYTextNSRangeFromCFRange(CFRange range) { + return NSMakeRange(range.location, range.length); +} + +/** + Convert NSRange to CFRange + @param range NSRange @return CFRange + */ +static inline CFRange YYTextCFRangeFromNSRange(NSRange range) { + return CFRangeMake(range.location, range.length); +} + + +/// Returns YES in App Extension. +BOOL YYTextIsAppExtension(void); + +/// Returns nil in App Extension. +UIApplication * _Nullable YYTextSharedApplication(void); + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/YYTextView.h b/YYText/include/YYTextView.h new file mode 100644 index 00000000..db5a275a --- /dev/null +++ b/YYText/include/YYTextView.h @@ -0,0 +1,410 @@ +// +// YYTextView.h +// YYText +// +// Created by ibireme on 15/2/25. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +#if __has_include() +#import +#import +#import +#else +#import "YYTextParser.h" +#import "YYTextLayout.h" +#import "YYTextAttribute.h" +#endif + +@class YYTextView; + +NS_ASSUME_NONNULL_BEGIN + +/** + The YYTextViewDelegate protocol defines a set of optional methods you can use + to receive editing-related messages for YYTextView objects. + + @discussion The API and behavior is similar to UITextViewDelegate, + see UITextViewDelegate's documentation for more information. + */ +@protocol YYTextViewDelegate +@optional +- (BOOL)textViewShouldBeginEditing:(YYTextView *)textView; +- (BOOL)textViewShouldEndEditing:(YYTextView *)textView; +- (void)textViewDidBeginEditing:(YYTextView *)textView; +- (void)textViewDidEndEditing:(YYTextView *)textView; +- (BOOL)textView:(YYTextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text; +- (void)textViewDidChange:(YYTextView *)textView; +- (void)textViewDidChangeSelection:(YYTextView *)textView; + +- (BOOL)textView:(YYTextView *)textView shouldTapHighlight:(YYTextHighlight *)highlight inRange:(NSRange)characterRange; +- (void)textView:(YYTextView *)textView didTapHighlight:(YYTextHighlight *)highlight inRange:(NSRange)characterRange rect:(CGRect)rect; +- (BOOL)textView:(YYTextView *)textView shouldLongPressHighlight:(YYTextHighlight *)highlight inRange:(NSRange)characterRange; +- (void)textView:(YYTextView *)textView didLongPressHighlight:(YYTextHighlight *)highlight inRange:(NSRange)characterRange rect:(CGRect)rect; +@end + + +#if !TARGET_INTERFACE_BUILDER + +/** + The YYTextView class implements the behavior for a scrollable, multiline text region. + + @discussion The API and behavior is similar to UITextView, but provides more features: + + * It extends the CoreText attributes to support more text effects. + * It allows to add UIImage, UIView and CALayer as text attachments. + * It allows to add 'highlight' link to some range of text to allow user interact with. + * It allows to add exclusion paths to control text container's shape. + * It supports vertical form layout to display and edit CJK text. + * It allows user to copy/paste image and attributed text from/to text view. + * It allows to set an attributed text as placeholder. + + See NSAttributedString+YYText.h for more convenience methods to set the attributes. + See YYTextAttribute.h and YYTextLayout.h for more information. + */ +@interface YYTextView : UIScrollView + + +#pragma mark - Accessing the Delegate +///============================================================================= +/// @name Accessing the Delegate +///============================================================================= + +@property (nullable, nonatomic, weak) id delegate; + + +#pragma mark - Configuring the Text Attributes +///============================================================================= +/// @name Configuring the Text Attributes +///============================================================================= + +/** + The text displayed by the text view. + Set a new value to this property also replaces the text in `attributedText`. + Get the value returns the plain text in `attributedText`. + */ +@property (null_resettable, nonatomic, copy) NSString *text; + +/** + The font of the text. Default is 12-point system font. + Set a new value to this property also causes the new font to be applied to the entire `attributedText`. + Get the value returns the font at the head of `attributedText`. + */ +@property (nullable, nonatomic, strong) UIFont *font; + +/** + The color of the text. Default is black. + Set a new value to this property also causes the new color to be applied to the entire `attributedText`. + Get the value returns the color at the head of `attributedText`. + */ +@property (nullable, nonatomic, strong) UIColor *textColor; + +/** + The technique to use for aligning the text. Default is NSTextAlignmentNatural. + Set a new value to this property also causes the new alignment to be applied to the entire `attributedText`. + Get the value returns the alignment at the head of `attributedText`. + */ +@property (nonatomic) NSTextAlignment textAlignment; + +/** + The text vertical aligmnent in container. Default is YYTextVerticalAlignmentTop. + */ +@property (nonatomic) YYTextVerticalAlignment textVerticalAlignment; + +/** + The types of data converted to clickable URLs in the text view. Default is UIDataDetectorTypeNone. + The tap or long press action should be handled by delegate. + */ +@property (nonatomic) UIDataDetectorTypes dataDetectorTypes; + +/** + The attributes to apply to links at normal state. Default is light blue color. + When a range of text is detected by the `dataDetectorTypes`, this value would be + used to modify the original attributes in the range. + */ +@property (nullable, nonatomic, copy) NSDictionary *linkTextAttributes; + +/** + The attributes to apply to links at highlight state. Default is a gray border. + When a range of text is detected by the `dataDetectorTypes` and the range was touched by user, + this value would be used to modify the original attributes in the range. + */ +@property (nullable, nonatomic, copy) NSDictionary *highlightTextAttributes; + +/** + The attributes to apply to new text being entered by the user. + When the text view's selection changes, this value is reset automatically. + */ +@property (nullable, nonatomic, copy) NSDictionary *typingAttributes; + +/** + The styled text displayed by the text view. + Set a new value to this property also replaces the value of the `text`, `font`, `textColor`, + `textAlignment` and other properties in text view. + + @discussion It only support the attributes declared in CoreText and YYTextAttribute. + See `NSAttributedString+YYText` for more convenience methods to set the attributes. + */ +@property (nullable, nonatomic, copy) NSAttributedString *attributedText; + +/** + When `text` or `attributedText` is changed, the parser will be called to modify the text. + It can be used to add code highlighting or emoticon replacement to text view. + The default value is nil. + + See `YYTextParser` protocol for more information. + */ +@property (nullable, nonatomic, strong) id textParser; + +/** + The current text layout in text view (readonly). + It can be used to query the text layout information. + */ +@property (nullable, nonatomic, strong, readonly) YYTextLayout *textLayout; + + +#pragma mark - Configuring the Placeholder +///============================================================================= +/// @name Configuring the Placeholder +///============================================================================= + +/** + The placeholder text displayed by the text view (when the text view is empty). + Set a new value to this property also replaces the text in `placeholderAttributedText`. + Get the value returns the plain text in `placeholderAttributedText`. + */ +@property (nullable, nonatomic, copy) NSString *placeholderText; + +/** + The font of the placeholder text. Default is same as `font` property. + Set a new value to this property also causes the new font to be applied to the entire `placeholderAttributedText`. + Get the value returns the font at the head of `placeholderAttributedText`. + */ +@property (nullable, nonatomic, strong) UIFont *placeholderFont; + +/** + The color of the placeholder text. Default is gray. + Set a new value to this property also causes the new color to be applied to the entire `placeholderAttributedText`. + Get the value returns the color at the head of `placeholderAttributedText`. + */ +@property (nullable, nonatomic, strong) UIColor *placeholderTextColor; + +/** + The styled placeholder text displayed by the text view (when the text view is empty). + Set a new value to this property also replaces the value of the `placeholderText`, + `placeholderFont`, `placeholderTextColor`. + + @discussion It only support the attributes declared in CoreText and YYTextAttribute. + See `NSAttributedString+YYText` for more convenience methods to set the attributes. + */ +@property (nullable, nonatomic, copy) NSAttributedString *placeholderAttributedText; + + +#pragma mark - Configuring the Text Container +///============================================================================= +/// @name Configuring the Text Container +///============================================================================= + +/** + The inset of the text container's layout area within the text view's content area. + */ +@property (nonatomic) UIEdgeInsets textContainerInset; + +/** + An array of UIBezierPath objects representing the exclusion paths inside the + receiver's bounding rectangle. Default value is nil. + */ +@property (nullable, nonatomic, copy) NSArray *exclusionPaths; + +/** + Whether the receiver's layout orientation is vertical form. Default is NO. + It may used to edit/display CJK text. + */ +@property (nonatomic, getter=isVerticalForm) BOOL verticalForm; + +/** + The text line position modifier used to modify the lines' position in layout. + See `YYTextLinePositionModifier` protocol for more information. + */ +@property (nullable, nonatomic, copy) id linePositionModifier; + +/** + The debug option to display CoreText layout result. + The default value is [YYTextDebugOption sharedDebugOption]. + */ +@property (nullable, nonatomic, copy) YYTextDebugOption *debugOption; + + +#pragma mark - Working with the Selection and Menu +///============================================================================= +/// @name Working with the Selection and Menu +///============================================================================= + +/** + Scrolls the receiver until the text in the specified range is visible. + */ +- (void)scrollRangeToVisible:(NSRange)range; + +/** + The current selection range of the receiver. + */ +@property (nonatomic) NSRange selectedRange; + +/** + A Boolean value indicating whether inserting text replaces the previous contents. + The default value is NO. + */ +@property (nonatomic) BOOL clearsOnInsertion; + +/** + A Boolean value indicating whether the receiver is selectable. Default is YES. + When the value of this property is NO, user cannot select content or edit text. + */ +@property (nonatomic, getter=isSelectable) BOOL selectable; + +/** + A Boolean value indicating whether the receiver is highlightable. Default is YES. + When the value of this property is NO, user cannot interact with the highlight range of text. + */ +@property (nonatomic, getter=isHighlightable) BOOL highlightable; + +/** + A Boolean value indicating whether the receiver is editable. Default is YES. + When the value of this property is NO, user cannot edit text. + */ +@property (nonatomic, getter=isEditable) BOOL editable; + +/** + A Boolean value indicating whether the receiver can paste image from pasteboard. Default is NO. + When the value of this property is YES, user can paste image from pasteboard via "paste" menu. + */ +@property (nonatomic) BOOL allowsPasteImage; + +/** + A Boolean value indicating whether the receiver can paste attributed text from pasteboard. Default is NO. + When the value of this property is YES, user can paste attributed text from pasteboard via "paste" menu. + */ +@property (nonatomic) BOOL allowsPasteAttributedString; + +/** + A Boolean value indicating whether the receiver can copy attributed text to pasteboard. Default is YES. + When the value of this property is YES, user can copy attributed text (with attachment image) + from text view to pasteboard via "copy" menu. + */ +@property (nonatomic) BOOL allowsCopyAttributedString; + + +#pragma mark - Manage the undo and redo +///============================================================================= +/// @name Manage the undo and redo +///============================================================================= + +/** + A Boolean value indicating whether the receiver can undo and redo typing with + shake gesture. The default value is YES. + */ +@property (nonatomic) BOOL allowsUndoAndRedo; + +/** + The maximum undo/redo level. The default value is 20. + */ +@property (nonatomic) NSUInteger maximumUndoLevel; + + +#pragma mark - Replacing the System Input Views +///============================================================================= +/// @name Replacing the System Input Views +///============================================================================= + +/** + The custom input view to display when the text view becomes the first responder. + It can be used to replace system keyboard. + + @discussion If set the value while first responder, it will not take effect until + 'reloadInputViews' is called. + */ +@property (nullable, nonatomic, readwrite, strong) __kindof UIView *inputView; + +/** + The custom accessory view to display when the text view becomes the first responder. + It can be used to add a toolbar at the top of keyboard. + + @discussion If set the value while first responder, it will not take effect until + 'reloadInputViews' is called. + */ +@property (nullable, nonatomic, readwrite, strong) __kindof UIView *inputAccessoryView; + +/** + If you use an custom accessory view without "inputAccessoryView" property, + you may set the accessory view's height. It may used by auto scroll calculation. + */ +@property (nonatomic) CGFloat extraAccessoryViewHeight; + +@end + + +#else // TARGET_INTERFACE_BUILDER +IB_DESIGNABLE +@interface YYTextView : UIScrollView +@property (null_resettable, nonatomic, copy) IBInspectable NSString *text; +@property (nullable, nonatomic, strong) IBInspectable UIColor *textColor; +@property (nullable, nonatomic, strong) IBInspectable NSString *fontName_; +@property (nonatomic) IBInspectable CGFloat fontSize_; +@property (nonatomic) IBInspectable BOOL fontIsBold_; +@property (nonatomic) IBInspectable NSTextAlignment textAlignment; +@property (nonatomic) IBInspectable YYTextVerticalAlignment textVerticalAlignment; +@property (nullable, nonatomic, copy) IBInspectable NSString *placeholderText; +@property (nullable, nonatomic, strong) IBInspectable UIColor *placeholderTextColor; +@property (nullable, nonatomic, strong) IBInspectable NSString *placeholderFontName_; +@property (nonatomic) IBInspectable CGFloat placeholderFontSize_; +@property (nonatomic) IBInspectable BOOL placeholderFontIsBold_; +@property (nonatomic, getter=isVerticalForm) IBInspectable BOOL verticalForm; +@property (nonatomic) IBInspectable BOOL clearsOnInsertion; +@property (nonatomic, getter=isSelectable) IBInspectable BOOL selectable; +@property (nonatomic, getter=isHighlightable) IBInspectable BOOL highlightable; +@property (nonatomic, getter=isEditable) IBInspectable BOOL editable; +@property (nonatomic) IBInspectable BOOL allowsPasteImage; +@property (nonatomic) IBInspectable BOOL allowsPasteAttributedString; +@property (nonatomic) IBInspectable BOOL allowsCopyAttributedString; +@property (nonatomic) IBInspectable BOOL allowsUndoAndRedo; +@property (nonatomic) IBInspectable NSUInteger maximumUndoLevel; +@property (nonatomic) IBInspectable CGFloat insetTop_; +@property (nonatomic) IBInspectable CGFloat insetBottom_; +@property (nonatomic) IBInspectable CGFloat insetLeft_; +@property (nonatomic) IBInspectable CGFloat insetRight_; +@property (nonatomic) IBInspectable BOOL debugEnabled_; +@property (nullable, nonatomic, weak) id delegate; +@property (nullable, nonatomic, strong) UIFont *font; +@property (nonatomic) UIDataDetectorTypes dataDetectorTypes; +@property (nullable, nonatomic, copy) NSDictionary *linkTextAttributes; +@property (nullable, nonatomic, copy) NSDictionary *highlightTextAttributes; +@property (nullable, nonatomic, copy) NSDictionary *typingAttributes; +@property (nullable, nonatomic, copy) NSAttributedString *attributedText; +@property (nullable, nonatomic, strong) id textParser; +@property (nullable, nonatomic, strong, readonly) YYTextLayout *textLayout; +@property (nullable, nonatomic, strong) UIFont *placeholderFont; +@property (nullable, nonatomic, copy) NSAttributedString *placeholderAttributedText; +@property (nonatomic) UIEdgeInsets textContainerInset; +@property (nullable, nonatomic, copy) NSArray *exclusionPaths; +@property (nullable, nonatomic, copy) id linePositionModifier; +@property (nullable, nonatomic, copy) YYTextDebugOption *debugOption; +- (void)scrollRangeToVisible:(NSRange)range; +@property (nonatomic) NSRange selectedRange; +@property (nullable, nonatomic, readwrite, strong) __kindof UIView *inputView; +@property (nullable, nonatomic, readwrite, strong) __kindof UIView *inputAccessoryView; +@property (nonatomic) CGFloat extraAccessoryViewHeight; +@end +#endif // !TARGET_INTERFACE_BUILDER + + +// Notifications, see UITextView's documentation for more information. +UIKIT_EXTERN NSString *const YYTextViewTextDidBeginEditingNotification; +UIKIT_EXTERN NSString *const YYTextViewTextDidChangeNotification; +UIKIT_EXTERN NSString *const YYTextViewTextDidEndEditingNotification; + +NS_ASSUME_NONNULL_END diff --git a/YYText/include/YYTextWeakProxy.h b/YYText/include/YYTextWeakProxy.h new file mode 100644 index 00000000..08343022 --- /dev/null +++ b/YYText/include/YYTextWeakProxy.h @@ -0,0 +1,61 @@ +// +// YYTextWeakProxy.h +// YYText +// +// Created by ibireme on 14/10/18. +// Copyright (c) 2015 ibireme. +// +// This source code is licensed under the MIT-style license found in the +// LICENSE file in the root directory of this source tree. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + A proxy used to hold a weak object. + It can be used to avoid retain cycles, such as the target in NSTimer or CADisplayLink. + + sample code: + + @implementation MyView { + NSTimer *_timer; + } + + - (void)initTimer { + YYTextWeakProxy *proxy = [YYTextWeakProxy proxyWithTarget:self]; + _timer = [NSTimer timerWithTimeInterval:0.1 target:proxy selector:@selector(tick:) userInfo:nil repeats:YES]; + } + + - (void)tick:(NSTimer *)timer {...} + @end + */ +@interface YYTextWeakProxy : NSProxy + +/** + The proxy target. + */ +@property (nullable, nonatomic, weak, readonly) id target; + +/** + Creates a new weak proxy for target. + + @param target Target object. + + @return A new proxy object. + */ +- (instancetype)initWithTarget:(id)target; + +/** + Creates a new weak proxy for target. + + @param target Target object. + + @return A new proxy object. + */ ++ (instancetype)proxyWithTarget:(id)target; + +@end + +NS_ASSUME_NONNULL_END