//
|
// NSAttributedString+YYText.m
|
// YYText <https://github.com/ibireme/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 "NSAttributedString+YYText.h"
|
#import "NSParagraphStyle+YYText.h"
|
#import "YYTextArchiver.h"
|
#import "YYTextRunDelegate.h"
|
#import "YYTextUtilities.h"
|
#import <CoreFoundation/CoreFoundation.h>
|
|
|
// Dummy class for category
|
@interface NSAttributedString_YYText : NSObject @end
|
@implementation NSAttributedString_YYText @end
|
|
|
static double _YYDeviceSystemVersion() {
|
static double version;
|
static dispatch_once_t onceToken;
|
dispatch_once(&onceToken, ^{
|
version = [UIDevice currentDevice].systemVersion.doubleValue;
|
});
|
return version;
|
}
|
|
#ifndef kSystemVersion
|
#define kSystemVersion _YYDeviceSystemVersion()
|
#endif
|
|
#ifndef kiOS6Later
|
#define kiOS6Later (kSystemVersion >= 6)
|
#endif
|
|
#ifndef kiOS7Later
|
#define kiOS7Later (kSystemVersion >= 7)
|
#endif
|
|
#ifndef kiOS8Later
|
#define kiOS8Later (kSystemVersion >= 8)
|
#endif
|
|
#ifndef kiOS9Later
|
#define kiOS9Later (kSystemVersion >= 9)
|
#endif
|
|
|
|
@implementation NSAttributedString (YYText)
|
|
- (NSData *)yy_archiveToData {
|
NSData *data = nil;
|
@try {
|
data = [YYTextArchiver archivedDataWithRootObject:self];
|
}
|
@catch (NSException *exception) {
|
NSLog(@"%@",exception);
|
}
|
return data;
|
}
|
|
+ (instancetype)yy_unarchiveFromData:(NSData *)data {
|
NSAttributedString *one = nil;
|
@try {
|
one = [YYTextUnarchiver unarchiveObjectWithData:data];
|
}
|
@catch (NSException *exception) {
|
NSLog(@"%@",exception);
|
}
|
return one;
|
}
|
|
- (NSDictionary *)yy_attributesAtIndex:(NSUInteger)index {
|
if (index > self.length || self.length == 0) return nil;
|
if (self.length > 0 && index == self.length) index--;
|
return [self attributesAtIndex:index effectiveRange:NULL];
|
}
|
|
- (id)yy_attribute:(NSString *)attributeName atIndex:(NSUInteger)index {
|
if (!attributeName) return nil;
|
if (index > self.length || self.length == 0) return nil;
|
if (self.length > 0 && index == self.length) index--;
|
return [self attribute:attributeName atIndex:index effectiveRange:NULL];
|
}
|
|
- (NSDictionary *)yy_attributes {
|
return [self yy_attributesAtIndex:0];
|
}
|
|
- (UIFont *)yy_font {
|
return [self yy_fontAtIndex:0];
|
}
|
|
- (UIFont *)yy_fontAtIndex:(NSUInteger)index {
|
/*
|
In iOS7 and later, UIFont is toll-free bridged to CTFontRef,
|
although Apple does not mention it in documentation.
|
|
In iOS6, UIFont is a wrapper for CTFontRef, so CoreText can alse use UIfont,
|
but UILabel/UITextView cannot use CTFontRef.
|
|
We use UIFont for both CoreText and UIKit.
|
*/
|
UIFont *font = [self yy_attribute:NSFontAttributeName atIndex:index];
|
if (kSystemVersion <= 6) {
|
if (font) {
|
if (CFGetTypeID((__bridge CFTypeRef)(font)) == CTFontGetTypeID()) {
|
CTFontRef CTFont = (__bridge CTFontRef)(font);
|
CFStringRef name = CTFontCopyPostScriptName(CTFont);
|
CGFloat size = CTFontGetSize(CTFont);
|
if (!name) {
|
font = nil;
|
} else {
|
font = [UIFont fontWithName:(__bridge NSString *)(name) size:size];
|
CFRelease(name);
|
}
|
}
|
}
|
}
|
return font;
|
}
|
|
- (NSNumber *)yy_kern {
|
return [self yy_kernAtIndex:0];
|
}
|
|
- (NSNumber *)yy_kernAtIndex:(NSUInteger)index {
|
return [self yy_attribute:NSKernAttributeName atIndex:index];
|
}
|
|
- (UIColor *)yy_color {
|
return [self yy_colorAtIndex:0];
|
}
|
|
- (UIColor *)yy_colorAtIndex:(NSUInteger)index {
|
UIColor *color = [self yy_attribute:NSForegroundColorAttributeName atIndex:index];
|
if (!color) {
|
CGColorRef ref = (__bridge CGColorRef)([self yy_attribute:(NSString *)kCTForegroundColorAttributeName atIndex:index]);
|
color = [UIColor colorWithCGColor:ref];
|
}
|
if (color && ![color isKindOfClass:[UIColor class]]) {
|
if (CFGetTypeID((__bridge CFTypeRef)(color)) == CGColorGetTypeID()) {
|
color = [UIColor colorWithCGColor:(__bridge CGColorRef)(color)];
|
} else {
|
color = nil;
|
}
|
}
|
return color;
|
}
|
|
- (UIColor *)yy_backgroundColor {
|
return [self yy_backgroundColorAtIndex:0];
|
}
|
|
- (UIColor *)yy_backgroundColorAtIndex:(NSUInteger)index {
|
return [self yy_attribute:NSBackgroundColorAttributeName atIndex:index];
|
}
|
|
- (NSNumber *)yy_strokeWidth {
|
return [self yy_strokeWidthAtIndex:0];
|
}
|
|
- (NSNumber *)yy_strokeWidthAtIndex:(NSUInteger)index {
|
return [self yy_attribute:NSStrokeWidthAttributeName atIndex:index];
|
}
|
|
- (UIColor *)yy_strokeColor {
|
return [self yy_strokeColorAtIndex:0];
|
}
|
|
- (UIColor *)yy_strokeColorAtIndex:(NSUInteger)index {
|
UIColor *color = [self yy_attribute:NSStrokeColorAttributeName atIndex:index];
|
if (!color) {
|
CGColorRef ref = (__bridge CGColorRef)([self yy_attribute:(NSString *)kCTStrokeColorAttributeName atIndex:index]);
|
color = [UIColor colorWithCGColor:ref];
|
}
|
return color;
|
}
|
|
- (NSShadow *)yy_shadow {
|
return [self yy_shadowAtIndex:0];
|
}
|
|
- (NSShadow *)yy_shadowAtIndex:(NSUInteger)index {
|
return [self yy_attribute:NSShadowAttributeName atIndex:index];
|
}
|
|
- (NSUnderlineStyle)yy_strikethroughStyle {
|
return [self yy_strikethroughStyleAtIndex:0];
|
}
|
|
- (NSUnderlineStyle)yy_strikethroughStyleAtIndex:(NSUInteger)index {
|
NSNumber *style = [self yy_attribute:NSStrikethroughStyleAttributeName atIndex:index];
|
return style.integerValue;
|
}
|
|
- (UIColor *)yy_strikethroughColor {
|
return [self yy_strikethroughColorAtIndex:0];
|
}
|
|
- (UIColor *)yy_strikethroughColorAtIndex:(NSUInteger)index {
|
if (kSystemVersion >= 7) {
|
return [self yy_attribute:NSStrikethroughColorAttributeName atIndex:index];
|
}
|
return nil;
|
}
|
|
- (NSUnderlineStyle)yy_underlineStyle {
|
return [self yy_underlineStyleAtIndex:0];
|
}
|
|
- (NSUnderlineStyle)yy_underlineStyleAtIndex:(NSUInteger)index {
|
NSNumber *style = [self yy_attribute:NSUnderlineStyleAttributeName atIndex:index];
|
return style.integerValue;
|
}
|
|
- (UIColor *)yy_underlineColor {
|
return [self yy_underlineColorAtIndex:0];
|
}
|
|
- (UIColor *)yy_underlineColorAtIndex:(NSUInteger)index {
|
UIColor *color = nil;
|
if (kSystemVersion >= 7) {
|
color = [self yy_attribute:NSUnderlineColorAttributeName atIndex:index];
|
}
|
if (!color) {
|
CGColorRef ref = (__bridge CGColorRef)([self yy_attribute:(NSString *)kCTUnderlineColorAttributeName atIndex:index]);
|
color = [UIColor colorWithCGColor:ref];
|
}
|
return color;
|
}
|
|
- (NSNumber *)yy_ligature {
|
return [self yy_ligatureAtIndex:0];
|
}
|
|
- (NSNumber *)yy_ligatureAtIndex:(NSUInteger)index {
|
return [self yy_attribute:NSLigatureAttributeName atIndex:index];
|
}
|
|
- (NSString *)yy_textEffect {
|
return [self yy_textEffectAtIndex:0];
|
}
|
|
- (NSString *)yy_textEffectAtIndex:(NSUInteger)index {
|
if (kSystemVersion >= 7) {
|
return [self yy_attribute:NSTextEffectAttributeName atIndex:index];
|
}
|
return nil;
|
}
|
|
- (NSNumber *)yy_obliqueness {
|
return [self yy_obliquenessAtIndex:0];
|
}
|
|
- (NSNumber *)yy_obliquenessAtIndex:(NSUInteger)index {
|
if (kSystemVersion >= 7) {
|
return [self yy_attribute:NSObliquenessAttributeName atIndex:index];
|
}
|
return nil;
|
}
|
|
- (NSNumber *)yy_expansion {
|
return [self yy_expansionAtIndex:0];
|
}
|
|
- (NSNumber *)yy_expansionAtIndex:(NSUInteger)index {
|
if (kSystemVersion >= 7) {
|
return [self yy_attribute:NSExpansionAttributeName atIndex:index];
|
}
|
return nil;
|
}
|
|
- (NSNumber *)yy_baselineOffset {
|
return [self yy_baselineOffsetAtIndex:0];
|
}
|
|
- (NSNumber *)yy_baselineOffsetAtIndex:(NSUInteger)index {
|
if (kSystemVersion >= 7) {
|
return [self yy_attribute:NSBaselineOffsetAttributeName atIndex:index];
|
}
|
return nil;
|
}
|
|
- (BOOL)yy_verticalGlyphForm {
|
return [self yy_verticalGlyphFormAtIndex:0];
|
}
|
|
- (BOOL)yy_verticalGlyphFormAtIndex:(NSUInteger)index {
|
NSNumber *num = [self yy_attribute:NSVerticalGlyphFormAttributeName atIndex:index];
|
return num.boolValue;
|
}
|
|
- (NSString *)yy_language {
|
return [self yy_languageAtIndex:0];
|
}
|
|
- (NSString *)yy_languageAtIndex:(NSUInteger)index {
|
if (kSystemVersion >= 7) {
|
return [self yy_attribute:(id)kCTLanguageAttributeName atIndex:index];
|
}
|
return nil;
|
}
|
|
- (NSArray *)yy_writingDirection {
|
return [self yy_writingDirectionAtIndex:0];
|
}
|
|
- (NSArray *)yy_writingDirectionAtIndex:(NSUInteger)index {
|
return [self yy_attribute:(id)kCTWritingDirectionAttributeName atIndex:index];
|
}
|
|
- (NSParagraphStyle *)yy_paragraphStyle {
|
return [self yy_paragraphStyleAtIndex:0];
|
}
|
|
- (NSParagraphStyle *)yy_paragraphStyleAtIndex:(NSUInteger)index {
|
/*
|
NSParagraphStyle is NOT toll-free bridged to CTParagraphStyleRef.
|
|
CoreText can use both NSParagraphStyle and CTParagraphStyleRef,
|
but UILabel/UITextView can only use NSParagraphStyle.
|
|
We use NSParagraphStyle in both CoreText and UIKit.
|
*/
|
NSParagraphStyle *style = [self yy_attribute:NSParagraphStyleAttributeName atIndex:index];
|
if (style) {
|
if (CFGetTypeID((__bridge CFTypeRef)(style)) == CTParagraphStyleGetTypeID()) { \
|
style = [NSParagraphStyle yy_styleWithCTStyle:(__bridge CTParagraphStyleRef)(style)];
|
}
|
}
|
return style;
|
}
|
|
#define ParagraphAttribute(_attr_) \
|
NSParagraphStyle *style = self.yy_paragraphStyle; \
|
if (!style) style = [NSParagraphStyle defaultParagraphStyle]; \
|
return style. _attr_;
|
|
#define ParagraphAttributeAtIndex(_attr_) \
|
NSParagraphStyle *style = [self yy_paragraphStyleAtIndex:index]; \
|
if (!style) style = [NSParagraphStyle defaultParagraphStyle]; \
|
return style. _attr_;
|
|
- (NSTextAlignment)yy_alignment {
|
ParagraphAttribute(alignment);
|
}
|
|
- (NSLineBreakMode)yy_lineBreakMode {
|
ParagraphAttribute(lineBreakMode);
|
}
|
|
- (CGFloat)yy_lineSpacing {
|
ParagraphAttribute(lineSpacing);
|
}
|
|
- (CGFloat)yy_paragraphSpacing {
|
ParagraphAttribute(paragraphSpacing);
|
}
|
|
- (CGFloat)yy_paragraphSpacingBefore {
|
ParagraphAttribute(paragraphSpacingBefore);
|
}
|
|
- (CGFloat)yy_firstLineHeadIndent {
|
ParagraphAttribute(firstLineHeadIndent);
|
}
|
|
- (CGFloat)yy_headIndent {
|
ParagraphAttribute(headIndent);
|
}
|
|
- (CGFloat)yy_tailIndent {
|
ParagraphAttribute(tailIndent);
|
}
|
|
- (CGFloat)yy_minimumLineHeight {
|
ParagraphAttribute(minimumLineHeight);
|
}
|
|
- (CGFloat)yy_maximumLineHeight {
|
ParagraphAttribute(maximumLineHeight);
|
}
|
|
- (CGFloat)yy_lineHeightMultiple {
|
ParagraphAttribute(lineHeightMultiple);
|
}
|
|
- (NSWritingDirection)yy_baseWritingDirection {
|
ParagraphAttribute(baseWritingDirection);
|
}
|
|
- (float)yy_hyphenationFactor {
|
ParagraphAttribute(hyphenationFactor);
|
}
|
|
- (CGFloat)yy_defaultTabInterval {
|
if (!kiOS7Later) return 0;
|
ParagraphAttribute(defaultTabInterval);
|
}
|
|
- (NSArray *)yy_tabStops {
|
if (!kiOS7Later) return nil;
|
ParagraphAttribute(tabStops);
|
}
|
|
- (NSTextAlignment)yy_alignmentAtIndex:(NSUInteger)index {
|
ParagraphAttributeAtIndex(alignment);
|
}
|
|
- (NSLineBreakMode)yy_lineBreakModeAtIndex:(NSUInteger)index {
|
ParagraphAttributeAtIndex(lineBreakMode);
|
}
|
|
- (CGFloat)yy_lineSpacingAtIndex:(NSUInteger)index {
|
ParagraphAttributeAtIndex(lineSpacing);
|
}
|
|
- (CGFloat)yy_paragraphSpacingAtIndex:(NSUInteger)index {
|
ParagraphAttributeAtIndex(paragraphSpacing);
|
}
|
|
- (CGFloat)yy_paragraphSpacingBeforeAtIndex:(NSUInteger)index {
|
ParagraphAttributeAtIndex(paragraphSpacingBefore);
|
}
|
|
- (CGFloat)yy_firstLineHeadIndentAtIndex:(NSUInteger)index {
|
ParagraphAttributeAtIndex(firstLineHeadIndent);
|
}
|
|
- (CGFloat)yy_headIndentAtIndex:(NSUInteger)index {
|
ParagraphAttributeAtIndex(headIndent);
|
}
|
|
- (CGFloat)yy_tailIndentAtIndex:(NSUInteger)index {
|
ParagraphAttributeAtIndex(tailIndent);
|
}
|
|
- (CGFloat)yy_minimumLineHeightAtIndex:(NSUInteger)index {
|
ParagraphAttributeAtIndex(minimumLineHeight);
|
}
|
|
- (CGFloat)yy_maximumLineHeightAtIndex:(NSUInteger)index {
|
ParagraphAttributeAtIndex(maximumLineHeight);
|
}
|
|
- (CGFloat)yy_lineHeightMultipleAtIndex:(NSUInteger)index {
|
ParagraphAttributeAtIndex(lineHeightMultiple);
|
}
|
|
- (NSWritingDirection)yy_baseWritingDirectionAtIndex:(NSUInteger)index {
|
ParagraphAttributeAtIndex(baseWritingDirection);
|
}
|
|
- (float)yy_hyphenationFactorAtIndex:(NSUInteger)index {
|
ParagraphAttributeAtIndex(hyphenationFactor);
|
}
|
|
- (CGFloat)yy_defaultTabIntervalAtIndex:(NSUInteger)index {
|
if (!kiOS7Later) return 0;
|
ParagraphAttributeAtIndex(defaultTabInterval);
|
}
|
|
- (NSArray *)yy_tabStopsAtIndex:(NSUInteger)index {
|
if (!kiOS7Later) return nil;
|
ParagraphAttributeAtIndex(tabStops);
|
}
|
|
#undef ParagraphAttribute
|
#undef ParagraphAttributeAtIndex
|
|
- (YYTextShadow *)yy_textShadow {
|
return [self yy_textShadowAtIndex:0];
|
}
|
|
- (YYTextShadow *)yy_textShadowAtIndex:(NSUInteger)index {
|
return [self yy_attribute:YYTextShadowAttributeName atIndex:index];
|
}
|
|
- (YYTextShadow *)yy_textInnerShadow {
|
return [self yy_textInnerShadowAtIndex:0];
|
}
|
|
- (YYTextShadow *)yy_textInnerShadowAtIndex:(NSUInteger)index {
|
return [self yy_attribute:YYTextInnerShadowAttributeName atIndex:index];
|
}
|
|
- (YYTextDecoration *)yy_textUnderline {
|
return [self yy_textUnderlineAtIndex:0];
|
}
|
|
- (YYTextDecoration *)yy_textUnderlineAtIndex:(NSUInteger)index {
|
return [self yy_attribute:YYTextUnderlineAttributeName atIndex:index];
|
}
|
|
- (YYTextDecoration *)yy_textStrikethrough {
|
return [self yy_textStrikethroughAtIndex:0];
|
}
|
|
- (YYTextDecoration *)yy_textStrikethroughAtIndex:(NSUInteger)index {
|
return [self yy_attribute:YYTextStrikethroughAttributeName atIndex:index];
|
}
|
|
- (YYTextBorder *)yy_textBorder {
|
return [self yy_textBorderAtIndex:0];
|
}
|
|
- (YYTextBorder *)yy_textBorderAtIndex:(NSUInteger)index {
|
return [self yy_attribute:YYTextBorderAttributeName atIndex:index];
|
}
|
|
- (YYTextBorder *)yy_textBackgroundBorder {
|
return [self yy_textBackgroundBorderAtIndex:0];
|
}
|
|
- (YYTextBorder *)yy_textBackgroundBorderAtIndex:(NSUInteger)index {
|
return [self yy_attribute:YYTextBackedStringAttributeName atIndex:index];
|
}
|
|
- (CGAffineTransform)yy_textGlyphTransform {
|
return [self yy_textGlyphTransformAtIndex:0];
|
}
|
|
- (CGAffineTransform)yy_textGlyphTransformAtIndex:(NSUInteger)index {
|
NSValue *value = [self yy_attribute:YYTextGlyphTransformAttributeName atIndex:index];
|
if (!value) return CGAffineTransformIdentity;
|
return [value CGAffineTransformValue];
|
}
|
|
- (NSString *)yy_plainTextForRange:(NSRange)range {
|
if (range.location == NSNotFound ||range.length == NSNotFound) return nil;
|
NSMutableString *result = [NSMutableString string];
|
if (range.length == 0) return result;
|
NSString *string = self.string;
|
[self enumerateAttribute:YYTextBackedStringAttributeName inRange:range options:kNilOptions usingBlock:^(id value, NSRange range, BOOL *stop) {
|
YYTextBackedString *backed = value;
|
if (backed && backed.string) {
|
[result appendString:backed.string];
|
} else {
|
[result appendString:[string substringWithRange:range]];
|
}
|
}];
|
return result;
|
}
|
|
+ (NSMutableAttributedString *)yy_attachmentStringWithContent:(id)content
|
contentMode:(UIViewContentMode)contentMode
|
width:(CGFloat)width
|
ascent:(CGFloat)ascent
|
descent:(CGFloat)descent {
|
NSMutableAttributedString *atr = [[NSMutableAttributedString alloc] initWithString:YYTextAttachmentToken];
|
|
YYTextAttachment *attach = [YYTextAttachment new];
|
attach.content = content;
|
attach.contentMode = contentMode;
|
[atr yy_setTextAttachment:attach range:NSMakeRange(0, atr.length)];
|
|
YYTextRunDelegate *delegate = [YYTextRunDelegate new];
|
delegate.width = width;
|
delegate.ascent = ascent;
|
delegate.descent = descent;
|
CTRunDelegateRef delegateRef = delegate.CTRunDelegate;
|
[atr yy_setRunDelegate:delegateRef range:NSMakeRange(0, atr.length)];
|
if (delegate) CFRelease(delegateRef);
|
|
return atr;
|
}
|
|
+ (NSMutableAttributedString *)yy_attachmentStringWithContent:(id)content
|
contentMode:(UIViewContentMode)contentMode
|
attachmentSize:(CGSize)attachmentSize
|
alignToFont:(UIFont *)font
|
alignment:(YYTextVerticalAlignment)alignment {
|
NSMutableAttributedString *atr = [[NSMutableAttributedString alloc] initWithString:YYTextAttachmentToken];
|
|
YYTextAttachment *attach = [YYTextAttachment new];
|
attach.content = content;
|
attach.contentMode = contentMode;
|
[atr yy_setTextAttachment:attach range:NSMakeRange(0, atr.length)];
|
|
YYTextRunDelegate *delegate = [YYTextRunDelegate new];
|
delegate.width = attachmentSize.width;
|
switch (alignment) {
|
case YYTextVerticalAlignmentTop: {
|
delegate.ascent = font.ascender;
|
delegate.descent = attachmentSize.height - font.ascender;
|
if (delegate.descent < 0) {
|
delegate.descent = 0;
|
delegate.ascent = attachmentSize.height;
|
}
|
} break;
|
case YYTextVerticalAlignmentCenter: {
|
CGFloat fontHeight = font.ascender - font.descender;
|
CGFloat yOffset = font.ascender - fontHeight * 0.5;
|
delegate.ascent = attachmentSize.height * 0.5 + yOffset;
|
delegate.descent = attachmentSize.height - delegate.ascent;
|
if (delegate.descent < 0) {
|
delegate.descent = 0;
|
delegate.ascent = attachmentSize.height;
|
}
|
} break;
|
case YYTextVerticalAlignmentBottom: {
|
delegate.ascent = attachmentSize.height + font.descender;
|
delegate.descent = -font.descender;
|
if (delegate.ascent < 0) {
|
delegate.ascent = 0;
|
delegate.descent = attachmentSize.height;
|
}
|
} break;
|
default: {
|
delegate.ascent = attachmentSize.height;
|
delegate.descent = 0;
|
} break;
|
}
|
|
CTRunDelegateRef delegateRef = delegate.CTRunDelegate;
|
[atr yy_setRunDelegate:delegateRef range:NSMakeRange(0, atr.length)];
|
if (delegate) CFRelease(delegateRef);
|
|
return atr;
|
}
|
|
+ (NSMutableAttributedString *)yy_attachmentStringWithEmojiImage:(UIImage *)image
|
fontSize:(CGFloat)fontSize {
|
if (!image || fontSize <= 0) return nil;
|
|
BOOL hasAnim = NO;
|
if (image.images.count > 1) {
|
hasAnim = YES;
|
} else if (NSProtocolFromString(@"YYAnimatedImage") &&
|
[image conformsToProtocol:NSProtocolFromString(@"YYAnimatedImage")]) {
|
NSNumber *frameCount = [image valueForKey:@"animatedImageFrameCount"];
|
if (frameCount.intValue > 1) hasAnim = YES;
|
}
|
|
CGFloat ascent = YYTextEmojiGetAscentWithFontSize(fontSize);
|
CGFloat descent = YYTextEmojiGetDescentWithFontSize(fontSize);
|
CGRect bounding = YYTextEmojiGetGlyphBoundingRectWithFontSize(fontSize);
|
|
YYTextRunDelegate *delegate = [YYTextRunDelegate new];
|
delegate.ascent = ascent;
|
delegate.descent = descent;
|
delegate.width = bounding.size.width + 2 * bounding.origin.x;
|
|
YYTextAttachment *attachment = [YYTextAttachment new];
|
attachment.contentMode = UIViewContentModeScaleAspectFit;
|
attachment.contentInsets = UIEdgeInsetsMake(ascent - (bounding.size.height + bounding.origin.y), bounding.origin.x, descent + bounding.origin.y, bounding.origin.x);
|
if (hasAnim) {
|
Class imageClass = NSClassFromString(@"YYAnimatedImageView");
|
if (!imageClass) imageClass = [UIImageView class];
|
UIImageView *view = (id)[imageClass new];
|
view.frame = bounding;
|
view.image = image;
|
view.contentMode = UIViewContentModeScaleAspectFit;
|
attachment.content = view;
|
} else {
|
attachment.content = image;
|
}
|
|
NSMutableAttributedString *atr = [[NSMutableAttributedString alloc] initWithString:YYTextAttachmentToken];
|
[atr yy_setTextAttachment:attachment range:NSMakeRange(0, atr.length)];
|
CTRunDelegateRef ctDelegate = delegate.CTRunDelegate;
|
[atr yy_setRunDelegate:ctDelegate range:NSMakeRange(0, atr.length)];
|
if (ctDelegate) CFRelease(ctDelegate);
|
|
return atr;
|
}
|
|
- (NSRange)yy_rangeOfAll {
|
return NSMakeRange(0, self.length);
|
}
|
|
- (BOOL)yy_isSharedAttributesInAllRange {
|
__block BOOL shared = YES;
|
__block NSDictionary *firstAttrs = nil;
|
[self enumerateAttributesInRange:self.yy_rangeOfAll options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
|
if (range.location == 0) {
|
firstAttrs = attrs;
|
} else {
|
if (firstAttrs.count != attrs.count) {
|
shared = NO;
|
*stop = YES;
|
} else if (firstAttrs) {
|
if (![firstAttrs isEqualToDictionary:attrs]) {
|
shared = NO;
|
*stop = YES;
|
}
|
}
|
}
|
}];
|
return shared;
|
}
|
|
- (BOOL)yy_canDrawWithUIKit {
|
static NSMutableSet *failSet;
|
static dispatch_once_t onceToken;
|
dispatch_once(&onceToken, ^{
|
failSet = [NSMutableSet new];
|
[failSet addObject:(id)kCTGlyphInfoAttributeName];
|
[failSet addObject:(id)kCTCharacterShapeAttributeName];
|
if (kiOS7Later) {
|
[failSet addObject:(id)kCTLanguageAttributeName];
|
}
|
[failSet addObject:(id)kCTRunDelegateAttributeName];
|
[failSet addObject:(id)kCTBaselineClassAttributeName];
|
[failSet addObject:(id)kCTBaselineInfoAttributeName];
|
[failSet addObject:(id)kCTBaselineReferenceInfoAttributeName];
|
if (kiOS8Later) {
|
[failSet addObject:(id)kCTRubyAnnotationAttributeName];
|
}
|
[failSet addObject:YYTextShadowAttributeName];
|
[failSet addObject:YYTextInnerShadowAttributeName];
|
[failSet addObject:YYTextUnderlineAttributeName];
|
[failSet addObject:YYTextStrikethroughAttributeName];
|
[failSet addObject:YYTextBorderAttributeName];
|
[failSet addObject:YYTextBackgroundBorderAttributeName];
|
[failSet addObject:YYTextBlockBorderAttributeName];
|
[failSet addObject:YYTextAttachmentAttributeName];
|
[failSet addObject:YYTextHighlightAttributeName];
|
[failSet addObject:YYTextGlyphTransformAttributeName];
|
});
|
|
#define Fail { result = NO; *stop = YES; return; }
|
__block BOOL result = YES;
|
[self enumerateAttributesInRange:self.yy_rangeOfAll options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
|
if (attrs.count == 0) return;
|
for (NSString *str in attrs.allKeys) {
|
if ([failSet containsObject:str]) Fail;
|
}
|
if (!kiOS7Later) {
|
UIFont *font = attrs[NSFontAttributeName];
|
if (CFGetTypeID((__bridge CFTypeRef)(font)) == CTFontGetTypeID()) Fail;
|
}
|
if (attrs[(id)kCTForegroundColorAttributeName] && !attrs[NSForegroundColorAttributeName]) Fail;
|
if (attrs[(id)kCTStrokeColorAttributeName] && !attrs[NSStrokeColorAttributeName]) Fail;
|
if (attrs[(id)kCTUnderlineColorAttributeName]) {
|
if (!kiOS7Later) Fail;
|
if (!attrs[NSUnderlineColorAttributeName]) Fail;
|
}
|
NSParagraphStyle *style = attrs[NSParagraphStyleAttributeName];
|
if (style && CFGetTypeID((__bridge CFTypeRef)(style)) == CTParagraphStyleGetTypeID()) Fail;
|
}];
|
return result;
|
#undef Fail
|
}
|
|
@end
|
|
@implementation NSMutableAttributedString (YYText)
|
|
- (void)yy_setAttributes:(NSDictionary *)attributes {
|
[self setYy_attributes:attributes];
|
}
|
|
- (void)setYy_attributes:(NSDictionary *)attributes {
|
if (attributes == (id)[NSNull null]) attributes = nil;
|
[self setAttributes:@{} range:NSMakeRange(0, self.length)];
|
[attributes enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
|
[self yy_setAttribute:key value:obj];
|
}];
|
}
|
|
- (void)yy_setAttribute:(NSString *)name value:(id)value {
|
[self yy_setAttribute:name value:value range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)yy_setAttribute:(NSString *)name value:(id)value range:(NSRange)range {
|
if (!name || [NSNull isEqual:name]) return;
|
if (value && ![NSNull isEqual:value]) [self addAttribute:name value:value range:range];
|
else [self removeAttribute:name range:range];
|
}
|
|
- (void)yy_removeAttributesInRange:(NSRange)range {
|
[self setAttributes:nil range:range];
|
}
|
|
#pragma mark - Property Setter
|
|
- (void)setYy_font:(UIFont *)font {
|
/*
|
In iOS7 and later, UIFont is toll-free bridged to CTFontRef,
|
although Apple does not mention it in documentation.
|
|
In iOS6, UIFont is a wrapper for CTFontRef, so CoreText can alse use UIfont,
|
but UILabel/UITextView cannot use CTFontRef.
|
|
We use UIFont for both CoreText and UIKit.
|
*/
|
[self yy_setFont:font range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_kern:(NSNumber *)kern {
|
[self yy_setKern:kern range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_color:(UIColor *)color {
|
[self yy_setColor:color range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_backgroundColor:(UIColor *)backgroundColor {
|
[self yy_setBackgroundColor:backgroundColor range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_strokeWidth:(NSNumber *)strokeWidth {
|
[self yy_setStrokeWidth:strokeWidth range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_strokeColor:(UIColor *)strokeColor {
|
[self yy_setStrokeColor:strokeColor range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_shadow:(NSShadow *)shadow {
|
[self yy_setShadow:shadow range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_strikethroughStyle:(NSUnderlineStyle)strikethroughStyle {
|
[self yy_setStrikethroughStyle:strikethroughStyle range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_strikethroughColor:(UIColor *)strikethroughColor {
|
[self yy_setStrikethroughColor:strikethroughColor range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_underlineStyle:(NSUnderlineStyle)underlineStyle {
|
[self yy_setUnderlineStyle:underlineStyle range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_underlineColor:(UIColor *)underlineColor {
|
[self yy_setUnderlineColor:underlineColor range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_ligature:(NSNumber *)ligature {
|
[self yy_setLigature:ligature range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_textEffect:(NSString *)textEffect {
|
[self yy_setTextEffect:textEffect range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_obliqueness:(NSNumber *)obliqueness {
|
[self yy_setObliqueness:obliqueness range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_expansion:(NSNumber *)expansion {
|
[self yy_setExpansion:expansion range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_baselineOffset:(NSNumber *)baselineOffset {
|
[self yy_setBaselineOffset:baselineOffset range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_verticalGlyphForm:(BOOL)verticalGlyphForm {
|
[self yy_setVerticalGlyphForm:verticalGlyphForm range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_language:(NSString *)language {
|
[self yy_setLanguage:language range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_writingDirection:(NSArray *)writingDirection {
|
[self yy_setWritingDirection:writingDirection range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_paragraphStyle:(NSParagraphStyle *)paragraphStyle {
|
/*
|
NSParagraphStyle is NOT toll-free bridged to CTParagraphStyleRef.
|
|
CoreText can use both NSParagraphStyle and CTParagraphStyleRef,
|
but UILabel/UITextView can only use NSParagraphStyle.
|
|
We use NSParagraphStyle in both CoreText and UIKit.
|
*/
|
[self yy_setParagraphStyle:paragraphStyle range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_alignment:(NSTextAlignment)alignment {
|
[self yy_setAlignment:alignment range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_baseWritingDirection:(NSWritingDirection)baseWritingDirection {
|
[self yy_setBaseWritingDirection:baseWritingDirection range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_lineSpacing:(CGFloat)lineSpacing {
|
[self yy_setLineSpacing:lineSpacing range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_paragraphSpacing:(CGFloat)paragraphSpacing {
|
[self yy_setParagraphSpacing:paragraphSpacing range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_paragraphSpacingBefore:(CGFloat)paragraphSpacingBefore {
|
[self yy_setParagraphSpacing:paragraphSpacingBefore range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_firstLineHeadIndent:(CGFloat)firstLineHeadIndent {
|
[self yy_setFirstLineHeadIndent:firstLineHeadIndent range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_headIndent:(CGFloat)headIndent {
|
[self yy_setHeadIndent:headIndent range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_tailIndent:(CGFloat)tailIndent {
|
[self yy_setTailIndent:tailIndent range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_lineBreakMode:(NSLineBreakMode)lineBreakMode {
|
[self yy_setLineBreakMode:lineBreakMode range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_minimumLineHeight:(CGFloat)minimumLineHeight {
|
[self yy_setMinimumLineHeight:minimumLineHeight range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_maximumLineHeight:(CGFloat)maximumLineHeight {
|
[self yy_setMaximumLineHeight:maximumLineHeight range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_lineHeightMultiple:(CGFloat)lineHeightMultiple {
|
[self yy_setLineHeightMultiple:lineHeightMultiple range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_hyphenationFactor:(float)hyphenationFactor {
|
[self yy_setHyphenationFactor:hyphenationFactor range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_defaultTabInterval:(CGFloat)defaultTabInterval {
|
[self yy_setDefaultTabInterval:defaultTabInterval range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_tabStops:(NSArray *)tabStops {
|
[self yy_setTabStops:tabStops range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_textShadow:(YYTextShadow *)textShadow {
|
[self yy_setTextShadow:textShadow range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_textInnerShadow:(YYTextShadow *)textInnerShadow {
|
[self yy_setTextInnerShadow:textInnerShadow range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_textUnderline:(YYTextDecoration *)textUnderline {
|
[self yy_setTextUnderline:textUnderline range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_textStrikethrough:(YYTextDecoration *)textStrikethrough {
|
[self yy_setTextStrikethrough:textStrikethrough range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_textBorder:(YYTextBorder *)textBorder {
|
[self yy_setTextBorder:textBorder range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_textBackgroundBorder:(YYTextBorder *)textBackgroundBorder {
|
[self yy_setTextBackgroundBorder:textBackgroundBorder range:NSMakeRange(0, self.length)];
|
}
|
|
- (void)setYy_textGlyphTransform:(CGAffineTransform)textGlyphTransform {
|
[self yy_setTextGlyphTransform:textGlyphTransform range:NSMakeRange(0, self.length)];
|
}
|
|
#pragma mark - Range Setter
|
|
- (void)yy_setFont:(UIFont *)font range:(NSRange)range {
|
/*
|
In iOS7 and later, UIFont is toll-free bridged to CTFontRef,
|
although Apple does not mention it in documentation.
|
|
In iOS6, UIFont is a wrapper for CTFontRef, so CoreText can alse use UIfont,
|
but UILabel/UITextView cannot use CTFontRef.
|
|
We use UIFont for both CoreText and UIKit.
|
*/
|
[self yy_setAttribute:NSFontAttributeName value:font range:range];
|
}
|
|
- (void)yy_setKern:(NSNumber *)kern range:(NSRange)range {
|
[self yy_setAttribute:NSKernAttributeName value:kern range:range];
|
}
|
|
- (void)yy_setColor:(UIColor *)color range:(NSRange)range {
|
[self yy_setAttribute:(id)kCTForegroundColorAttributeName value:(id)color.CGColor range:range];
|
[self yy_setAttribute:NSForegroundColorAttributeName value:color range:range];
|
}
|
|
- (void)yy_setBackgroundColor:(UIColor *)backgroundColor range:(NSRange)range {
|
[self yy_setAttribute:NSBackgroundColorAttributeName value:backgroundColor range:range];
|
}
|
|
- (void)yy_setStrokeWidth:(NSNumber *)strokeWidth range:(NSRange)range {
|
[self yy_setAttribute:NSStrokeWidthAttributeName value:strokeWidth range:range];
|
}
|
|
- (void)yy_setStrokeColor:(UIColor *)strokeColor range:(NSRange)range {
|
[self yy_setAttribute:(id)kCTStrokeColorAttributeName value:(id)strokeColor.CGColor range:range];
|
[self yy_setAttribute:NSStrokeColorAttributeName value:strokeColor range:range];
|
}
|
|
- (void)yy_setShadow:(NSShadow *)shadow range:(NSRange)range {
|
[self yy_setAttribute:NSShadowAttributeName value:shadow range:range];
|
}
|
|
- (void)yy_setStrikethroughStyle:(NSUnderlineStyle)strikethroughStyle range:(NSRange)range {
|
NSNumber *style = strikethroughStyle == 0 ? nil : @(strikethroughStyle);
|
[self yy_setAttribute:NSStrikethroughStyleAttributeName value:style range:range];
|
}
|
|
- (void)yy_setStrikethroughColor:(UIColor *)strikethroughColor range:(NSRange)range {
|
if (kSystemVersion >= 7) {
|
[self yy_setAttribute:NSStrikethroughColorAttributeName value:strikethroughColor range:range];
|
}
|
}
|
|
- (void)yy_setUnderlineStyle:(NSUnderlineStyle)underlineStyle range:(NSRange)range {
|
NSNumber *style = underlineStyle == 0 ? nil : @(underlineStyle);
|
[self yy_setAttribute:NSUnderlineStyleAttributeName value:style range:range];
|
}
|
|
- (void)yy_setUnderlineColor:(UIColor *)underlineColor range:(NSRange)range {
|
[self yy_setAttribute:(id)kCTUnderlineColorAttributeName value:(id)underlineColor.CGColor range:range];
|
if (kSystemVersion >= 7) {
|
[self yy_setAttribute:NSUnderlineColorAttributeName value:underlineColor range:range];
|
}
|
}
|
|
- (void)yy_setLigature:(NSNumber *)ligature range:(NSRange)range {
|
[self yy_setAttribute:NSLigatureAttributeName value:ligature range:range];
|
}
|
|
- (void)yy_setTextEffect:(NSString *)textEffect range:(NSRange)range {
|
if (kSystemVersion >= 7) {
|
[self yy_setAttribute:NSTextEffectAttributeName value:textEffect range:range];
|
}
|
}
|
|
- (void)yy_setObliqueness:(NSNumber *)obliqueness range:(NSRange)range {
|
if (kSystemVersion >= 7) {
|
[self yy_setAttribute:NSObliquenessAttributeName value:obliqueness range:range];
|
}
|
}
|
|
- (void)yy_setExpansion:(NSNumber *)expansion range:(NSRange)range {
|
if (kSystemVersion >= 7) {
|
[self yy_setAttribute:NSExpansionAttributeName value:expansion range:range];
|
}
|
}
|
|
- (void)yy_setBaselineOffset:(NSNumber *)baselineOffset range:(NSRange)range {
|
if (kSystemVersion >= 7) {
|
[self yy_setAttribute:NSBaselineOffsetAttributeName value:baselineOffset range:range];
|
}
|
}
|
|
- (void)yy_setVerticalGlyphForm:(BOOL)verticalGlyphForm range:(NSRange)range {
|
NSNumber *v = verticalGlyphForm ? @(YES) : nil;
|
[self yy_setAttribute:NSVerticalGlyphFormAttributeName value:v range:range];
|
}
|
|
- (void)yy_setLanguage:(NSString *)language range:(NSRange)range {
|
if (kSystemVersion >= 7) {
|
[self yy_setAttribute:(id)kCTLanguageAttributeName value:language range:range];
|
}
|
}
|
|
- (void)yy_setWritingDirection:(NSArray *)writingDirection range:(NSRange)range {
|
[self yy_setAttribute:(id)kCTWritingDirectionAttributeName value:writingDirection range:range];
|
}
|
|
- (void)yy_setParagraphStyle:(NSParagraphStyle *)paragraphStyle range:(NSRange)range {
|
/*
|
NSParagraphStyle is NOT toll-free bridged to CTParagraphStyleRef.
|
|
CoreText can use both NSParagraphStyle and CTParagraphStyleRef,
|
but UILabel/UITextView can only use NSParagraphStyle.
|
|
We use NSParagraphStyle in both CoreText and UIKit.
|
*/
|
[self yy_setAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range];
|
}
|
|
#define ParagraphStyleSet(_attr_) \
|
[self enumerateAttribute:NSParagraphStyleAttributeName \
|
inRange:range \
|
options:kNilOptions \
|
usingBlock: ^(NSParagraphStyle *value, NSRange subRange, BOOL *stop) { \
|
NSMutableParagraphStyle *style = nil; \
|
if (value) { \
|
if (CFGetTypeID((__bridge CFTypeRef)(value)) == CTParagraphStyleGetTypeID()) { \
|
value = [NSParagraphStyle yy_styleWithCTStyle:(__bridge CTParagraphStyleRef)(value)]; \
|
} \
|
if (value. _attr_ == _attr_) return; \
|
if ([value isKindOfClass:[NSMutableParagraphStyle class]]) { \
|
style = (id)value; \
|
} else { \
|
style = value.mutableCopy; \
|
} \
|
} else { \
|
if ([NSParagraphStyle defaultParagraphStyle]. _attr_ == _attr_) return; \
|
style = [NSParagraphStyle defaultParagraphStyle].mutableCopy; \
|
} \
|
style. _attr_ = _attr_; \
|
[self yy_setParagraphStyle:style range:subRange]; \
|
}];
|
|
- (void)yy_setAlignment:(NSTextAlignment)alignment range:(NSRange)range {
|
ParagraphStyleSet(alignment);
|
}
|
|
- (void)yy_setBaseWritingDirection:(NSWritingDirection)baseWritingDirection range:(NSRange)range {
|
ParagraphStyleSet(baseWritingDirection);
|
}
|
|
- (void)yy_setLineSpacing:(CGFloat)lineSpacing range:(NSRange)range {
|
ParagraphStyleSet(lineSpacing);
|
}
|
|
- (void)yy_setParagraphSpacing:(CGFloat)paragraphSpacing range:(NSRange)range {
|
ParagraphStyleSet(paragraphSpacing);
|
}
|
|
- (void)yy_setParagraphSpacingBefore:(CGFloat)paragraphSpacingBefore range:(NSRange)range {
|
ParagraphStyleSet(paragraphSpacingBefore);
|
}
|
|
- (void)yy_setFirstLineHeadIndent:(CGFloat)firstLineHeadIndent range:(NSRange)range {
|
ParagraphStyleSet(firstLineHeadIndent);
|
}
|
|
- (void)yy_setHeadIndent:(CGFloat)headIndent range:(NSRange)range {
|
ParagraphStyleSet(headIndent);
|
}
|
|
- (void)yy_setTailIndent:(CGFloat)tailIndent range:(NSRange)range {
|
ParagraphStyleSet(tailIndent);
|
}
|
|
- (void)yy_setLineBreakMode:(NSLineBreakMode)lineBreakMode range:(NSRange)range {
|
ParagraphStyleSet(lineBreakMode);
|
}
|
|
- (void)yy_setMinimumLineHeight:(CGFloat)minimumLineHeight range:(NSRange)range {
|
ParagraphStyleSet(minimumLineHeight);
|
}
|
|
- (void)yy_setMaximumLineHeight:(CGFloat)maximumLineHeight range:(NSRange)range {
|
ParagraphStyleSet(maximumLineHeight);
|
}
|
|
- (void)yy_setLineHeightMultiple:(CGFloat)lineHeightMultiple range:(NSRange)range {
|
ParagraphStyleSet(lineHeightMultiple);
|
}
|
|
- (void)yy_setHyphenationFactor:(float)hyphenationFactor range:(NSRange)range {
|
ParagraphStyleSet(hyphenationFactor);
|
}
|
|
- (void)yy_setDefaultTabInterval:(CGFloat)defaultTabInterval range:(NSRange)range {
|
if (!kiOS7Later) return;
|
ParagraphStyleSet(defaultTabInterval);
|
}
|
|
- (void)yy_setTabStops:(NSArray *)tabStops range:(NSRange)range {
|
if (!kiOS7Later) return;
|
ParagraphStyleSet(tabStops);
|
}
|
|
#undef ParagraphStyleSet
|
|
- (void)yy_setSuperscript:(NSNumber *)superscript range:(NSRange)range {
|
if ([superscript isEqualToNumber:@(0)]) {
|
superscript = nil;
|
}
|
[self yy_setAttribute:(id)kCTSuperscriptAttributeName value:superscript range:range];
|
}
|
|
- (void)yy_setGlyphInfo:(CTGlyphInfoRef)glyphInfo range:(NSRange)range {
|
[self yy_setAttribute:(id)kCTGlyphInfoAttributeName value:(__bridge id)glyphInfo range:range];
|
}
|
|
- (void)yy_setCharacterShape:(NSNumber *)characterShape range:(NSRange)range {
|
[self yy_setAttribute:(id)kCTCharacterShapeAttributeName value:characterShape range:range];
|
}
|
|
- (void)yy_setRunDelegate:(CTRunDelegateRef)runDelegate range:(NSRange)range {
|
[self yy_setAttribute:(id)kCTRunDelegateAttributeName value:(__bridge id)runDelegate range:range];
|
}
|
|
- (void)yy_setBaselineClass:(CFStringRef)baselineClass range:(NSRange)range {
|
[self yy_setAttribute:(id)kCTBaselineClassAttributeName value:(__bridge id)baselineClass range:range];
|
}
|
|
- (void)yy_setBaselineInfo:(CFDictionaryRef)baselineInfo range:(NSRange)range {
|
[self yy_setAttribute:(id)kCTBaselineInfoAttributeName value:(__bridge id)baselineInfo range:range];
|
}
|
|
- (void)yy_setBaselineReferenceInfo:(CFDictionaryRef)referenceInfo range:(NSRange)range {
|
[self yy_setAttribute:(id)kCTBaselineReferenceInfoAttributeName value:(__bridge id)referenceInfo range:range];
|
}
|
|
- (void)yy_setRubyAnnotation:(CTRubyAnnotationRef)ruby range:(NSRange)range {
|
if (kSystemVersion >= 8) {
|
[self yy_setAttribute:(id)kCTRubyAnnotationAttributeName value:(__bridge id)ruby range:range];
|
}
|
}
|
|
- (void)yy_setAttachment:(NSTextAttachment *)attachment range:(NSRange)range {
|
if (kSystemVersion >= 7) {
|
[self yy_setAttribute:NSAttachmentAttributeName value:attachment range:range];
|
}
|
}
|
|
- (void)yy_setLink:(id)link range:(NSRange)range {
|
if (kSystemVersion >= 7) {
|
[self yy_setAttribute:NSLinkAttributeName value:link range:range];
|
}
|
}
|
|
- (void)yy_setTextBackedString:(YYTextBackedString *)textBackedString range:(NSRange)range {
|
[self yy_setAttribute:YYTextBackedStringAttributeName value:textBackedString range:range];
|
}
|
|
- (void)yy_setTextBinding:(YYTextBinding *)textBinding range:(NSRange)range {
|
[self yy_setAttribute:YYTextBindingAttributeName value:textBinding range:range];
|
}
|
|
- (void)yy_setTextShadow:(YYTextShadow *)textShadow range:(NSRange)range {
|
[self yy_setAttribute:YYTextShadowAttributeName value:textShadow range:range];
|
}
|
|
- (void)yy_setTextInnerShadow:(YYTextShadow *)textInnerShadow range:(NSRange)range {
|
[self yy_setAttribute:YYTextInnerShadowAttributeName value:textInnerShadow range:range];
|
}
|
|
- (void)yy_setTextUnderline:(YYTextDecoration *)textUnderline range:(NSRange)range {
|
[self yy_setAttribute:YYTextUnderlineAttributeName value:textUnderline range:range];
|
}
|
|
- (void)yy_setTextStrikethrough:(YYTextDecoration *)textStrikethrough range:(NSRange)range {
|
[self yy_setAttribute:YYTextStrikethroughAttributeName value:textStrikethrough range:range];
|
}
|
|
- (void)yy_setTextBorder:(YYTextBorder *)textBorder range:(NSRange)range {
|
[self yy_setAttribute:YYTextBorderAttributeName value:textBorder range:range];
|
}
|
|
- (void)yy_setTextBackgroundBorder:(YYTextBorder *)textBackgroundBorder range:(NSRange)range {
|
[self yy_setAttribute:YYTextBackgroundBorderAttributeName value:textBackgroundBorder range:range];
|
}
|
|
- (void)yy_setTextAttachment:(YYTextAttachment *)textAttachment range:(NSRange)range {
|
[self yy_setAttribute:YYTextAttachmentAttributeName value:textAttachment range:range];
|
}
|
|
- (void)yy_setTextHighlight:(YYTextHighlight *)textHighlight range:(NSRange)range {
|
[self yy_setAttribute:YYTextHighlightAttributeName value:textHighlight range:range];
|
}
|
|
- (void)yy_setTextBlockBorder:(YYTextBorder *)textBlockBorder range:(NSRange)range {
|
[self yy_setAttribute:YYTextBlockBorderAttributeName value:textBlockBorder range:range];
|
}
|
|
- (void)yy_setTextRubyAnnotation:(YYTextRubyAnnotation *)ruby range:(NSRange)range {
|
if (kiOS8Later) {
|
CTRubyAnnotationRef rubyRef = [ruby CTRubyAnnotation];
|
[self yy_setRubyAnnotation:rubyRef range:range];
|
if (rubyRef) CFRelease(rubyRef);
|
}
|
}
|
|
- (void)yy_setTextGlyphTransform:(CGAffineTransform)textGlyphTransform range:(NSRange)range {
|
NSValue *value = CGAffineTransformIsIdentity(textGlyphTransform) ? nil : [NSValue valueWithCGAffineTransform:textGlyphTransform];
|
[self yy_setAttribute:YYTextGlyphTransformAttributeName value:value range:range];
|
}
|
|
- (void)yy_setTextHighlightRange:(NSRange)range
|
color:(UIColor *)color
|
backgroundColor:(UIColor *)backgroundColor
|
userInfo:(NSDictionary *)userInfo
|
tapAction:(YYTextAction)tapAction
|
longPressAction:(YYTextAction)longPressAction {
|
YYTextHighlight *highlight = [YYTextHighlight highlightWithBackgroundColor:backgroundColor];
|
highlight.userInfo = userInfo;
|
highlight.tapAction = tapAction;
|
highlight.longPressAction = longPressAction;
|
if (color) [self yy_setColor:color range:range];
|
[self yy_setTextHighlight:highlight range:range];
|
}
|
|
- (void)yy_setTextHighlightRange:(NSRange)range
|
color:(UIColor *)color
|
backgroundColor:(UIColor *)backgroundColor
|
tapAction:(YYTextAction)tapAction {
|
[self yy_setTextHighlightRange:range
|
color:color
|
backgroundColor:backgroundColor
|
userInfo:nil
|
tapAction:tapAction
|
longPressAction:nil];
|
}
|
|
- (void)yy_setTextHighlightRange:(NSRange)range
|
color:(UIColor *)color
|
backgroundColor:(UIColor *)backgroundColor
|
userInfo:(NSDictionary *)userInfo {
|
[self yy_setTextHighlightRange:range
|
color:color
|
backgroundColor:backgroundColor
|
userInfo:userInfo
|
tapAction:nil
|
longPressAction:nil];
|
}
|
|
- (void)yy_insertString:(NSString *)string atIndex:(NSUInteger)location {
|
[self replaceCharactersInRange:NSMakeRange(location, 0) withString:string];
|
[self yy_removeDiscontinuousAttributesInRange:NSMakeRange(location, string.length)];
|
}
|
|
- (void)yy_appendString:(NSString *)string {
|
NSUInteger length = self.length;
|
[self replaceCharactersInRange:NSMakeRange(length, 0) withString:string];
|
[self yy_removeDiscontinuousAttributesInRange:NSMakeRange(length, string.length)];
|
}
|
|
- (void)yy_setClearColorToJoinedEmoji {
|
NSString *str = self.string;
|
if (str.length < 8) return;
|
|
// Most string do not contains the joined-emoji, test the joiner first.
|
BOOL containsJoiner = NO;
|
{
|
CFStringRef cfStr = (__bridge CFStringRef)str;
|
BOOL needFree = NO;
|
UniChar *chars = NULL;
|
chars = (void *)CFStringGetCharactersPtr(cfStr);
|
if (!chars) {
|
chars = malloc(str.length * sizeof(UniChar));
|
if (chars) {
|
needFree = YES;
|
CFStringGetCharacters(cfStr, CFRangeMake(0, str.length), chars);
|
}
|
}
|
if (!chars) { // fail to get unichar..
|
containsJoiner = YES;
|
} else {
|
for (int i = 0, max = (int)str.length; i < max; i++) {
|
if (chars[i] == 0x200D) { // 'ZERO WIDTH JOINER' (U+200D)
|
containsJoiner = YES;
|
break;
|
}
|
}
|
if (needFree) free(chars);
|
}
|
}
|
if (!containsJoiner) return;
|
|
// NSRegularExpression is designed to be immutable and thread safe.
|
static NSRegularExpression *regex;
|
static dispatch_once_t onceToken;
|
dispatch_once(&onceToken, ^{
|
regex = [NSRegularExpression regularExpressionWithPattern:@"((👨👩👧👦|👨👩👦👦|👨👩👧👧|👩👩👧👦|👩👩👦👦|👩👩👧👧|👨👨👧👦|👨👨👦👦|👨👨👧👧)+|(👨👩👧|👩👩👦|👩👩👧|👨👨👦|👨👨👧))" options:kNilOptions error:nil];
|
});
|
|
UIColor *clear = [UIColor clearColor];
|
[regex enumerateMatchesInString:str options:kNilOptions range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
[self yy_setColor:clear range:result.range];
|
}];
|
}
|
|
- (void)yy_removeDiscontinuousAttributesInRange:(NSRange)range {
|
NSArray *keys = [NSMutableAttributedString yy_allDiscontinuousAttributeKeys];
|
for (NSString *key in keys) {
|
[self removeAttribute:key range:range];
|
}
|
}
|
|
+ (NSArray *)yy_allDiscontinuousAttributeKeys {
|
static NSMutableArray *keys;
|
static dispatch_once_t onceToken;
|
dispatch_once(&onceToken, ^{
|
keys = @[(id)kCTSuperscriptAttributeName,
|
(id)kCTRunDelegateAttributeName,
|
YYTextBackedStringAttributeName,
|
YYTextBindingAttributeName,
|
YYTextAttachmentAttributeName].mutableCopy;
|
if (kiOS8Later) {
|
[keys addObject:(id)kCTRubyAnnotationAttributeName];
|
}
|
if (kiOS7Later) {
|
[keys addObject:NSAttachmentAttributeName];
|
}
|
});
|
return keys;
|
}
|
|
@end
|