├── .gitignore ├── LICENSE ├── README.md ├── READMEIMAGE └── TQRichTextView.png └── TQRichTextViewDemo ├── TQRichTextView ├── Images │ ├── [cry].png │ ├── [hei].png │ └── [smile].png ├── TQRichTextRun.h ├── TQRichTextRun.m ├── TQRichTextRunDelegate.h ├── TQRichTextRunDelegate.m ├── TQRichTextRunEmoji.h ├── TQRichTextRunEmoji.m ├── TQRichTextRunURL.h ├── TQRichTextRunURL.m ├── TQRichTextView.h └── TQRichTextView.m ├── TQRichTextViewDemo.xcodeproj └── project.pbxproj ├── TQRichTextViewDemo ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── TQAppDelegate.h ├── TQAppDelegate.m ├── TQRichTextViewDemo-Info.plist ├── TQRichTextViewDemo-Prefix.pch ├── TQViewController.h ├── TQViewController.m ├── en.lproj │ └── InfoPlist.strings └── main.m └── TQRichTextViewDemoTests ├── TQRichTextViewDemoTests-Info.plist ├── TQRichTextViewDemoTests.m └── en.lproj └── InfoPlist.strings /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | build/ 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | *.xcworkspace 13 | !default.xcworkspace 14 | xcuserdata 15 | profile 16 | *.moved-aside 17 | DerivedData 18 | .idea/ 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TinyQ/TQRichTextView/e2adc6eff53c2bf3b6520fcf91d31d351c5c1fb3/LICENSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | TQRichTextView 2 | ============== 3 | 4 | 富文本视图控件 5 | 6 | ![Image text](READMEIMAGE/TQRichTextView.png) 7 | -------------------------------------------------------------------------------- /READMEIMAGE/TQRichTextView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TinyQ/TQRichTextView/e2adc6eff53c2bf3b6520fcf91d31d351c5c1fb3/READMEIMAGE/TQRichTextView.png -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextView/Images/[cry].png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TinyQ/TQRichTextView/e2adc6eff53c2bf3b6520fcf91d31d351c5c1fb3/TQRichTextViewDemo/TQRichTextView/Images/[cry].png -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextView/Images/[hei].png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TinyQ/TQRichTextView/e2adc6eff53c2bf3b6520fcf91d31d351c5c1fb3/TQRichTextViewDemo/TQRichTextView/Images/[hei].png -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextView/Images/[smile].png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TinyQ/TQRichTextView/e2adc6eff53c2bf3b6520fcf91d31d351c5c1fb3/TQRichTextViewDemo/TQRichTextView/Images/[smile].png -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextView/TQRichTextRun.h: -------------------------------------------------------------------------------- 1 | // 2 | // TQRichTextRun.h 3 | // TQRichTextViewDemo 4 | // 5 | // Created by fuqiang on 2/27/14. 6 | // Copyright (c) 2014 fuqiang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface TQRichTextRun : UIResponder 13 | 14 | /** 15 | * 文本单元内容 16 | */ 17 | @property (nonatomic,copy ) NSString *text; 18 | 19 | /** 20 | * 文本单元字体 21 | */ 22 | @property (nonatomic,strong) UIFont *font; 23 | 24 | /** 25 | * 文本单元在字符串中的位置 26 | */ 27 | @property (nonatomic,assign) NSRange range; 28 | 29 | /** 30 | * 是否自己绘制自己 31 | */ 32 | @property(nonatomic,getter = isDrawSelf) BOOL drawSelf; 33 | 34 | /** 35 | * 向字符串中添加相关Run类型属性 36 | */ 37 | - (void)decorateToAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range; 38 | 39 | /** 40 | * 绘制Run内容 41 | */ 42 | - (void)drawRunWithRect:(CGRect)rect; 43 | 44 | @end 45 | 46 | extern NSString * const kTQRichTextRunSelfAttributedName; -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextView/TQRichTextRun.m: -------------------------------------------------------------------------------- 1 | // 2 | // TQRichTextRun.m 3 | // TQRichTextViewDemo 4 | // 5 | // Created by fuqiang on 2/27/14. 6 | // Copyright (c) 2014 fuqiang. All rights reserved. 7 | // 8 | 9 | #import "TQRichTextRun.h" 10 | 11 | NSString * const kTQRichTextRunSelfAttributedName = @"kTQRichTextRunSelfAttributedName"; 12 | 13 | @implementation TQRichTextRun 14 | 15 | /** 16 | * 向字符串中添加相关Run类型属性 17 | */ 18 | - (void)decorateToAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range 19 | { 20 | [attributedString addAttribute:kTQRichTextRunSelfAttributedName value:self range:range]; 21 | 22 | self.font = [attributedString attribute:NSFontAttributeName atIndex:0 longestEffectiveRange:nil inRange:range]; 23 | } 24 | 25 | /** 26 | * 绘制Run内容 27 | */ 28 | - (void)drawRunWithRect:(CGRect)rect 29 | { 30 | 31 | } 32 | 33 | @end 34 | 35 | -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextView/TQRichTextRunDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // TQRichTextRunDelegate.h 3 | // TQRichTextViewDemo 4 | // 5 | // Created by fuqiang on 2/28/14. 6 | // Copyright (c) 2014 fuqiang. All rights reserved. 7 | // 8 | 9 | #import "TQRichTextRun.h" 10 | 11 | @interface TQRichTextRunDelegate : TQRichTextRun 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextView/TQRichTextRunDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // TQRichTextRunDelegate.m 3 | // TQRichTextViewDemo 4 | // 5 | // Created by fuqiang on 2/28/14. 6 | // Copyright (c) 2014 fuqiang. All rights reserved. 7 | // 8 | 9 | #import "TQRichTextRunDelegate.h" 10 | 11 | @implementation TQRichTextRunDelegate 12 | 13 | /** 14 | * 向字符串中添加相关Run类型属性 15 | */ 16 | - (void)decorateToAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range 17 | { 18 | [super decorateToAttributedString:attributedString range:range]; 19 | 20 | CTRunDelegateCallbacks callbacks; 21 | callbacks.version = kCTRunDelegateVersion1; 22 | callbacks.dealloc = TQRichTextRunDelegateDeallocCallback; 23 | callbacks.getAscent = TQRichTextRunDelegateGetAscentCallback; 24 | callbacks.getDescent = TQRichTextRunDelegateGetDescentCallback; 25 | callbacks.getWidth = TQRichTextRunDelegateGetWidthCallback; 26 | 27 | CTRunDelegateRef runDelegate = CTRunDelegateCreate(&callbacks, (__bridge_retained void*)self); 28 | [attributedString addAttribute:(NSString *)kCTRunDelegateAttributeName value:(__bridge id)runDelegate range:range]; 29 | CFRelease(runDelegate); 30 | 31 | [attributedString addAttribute:(NSString *)kCTForegroundColorAttributeName value:(id)[UIColor clearColor].CGColor range:range]; 32 | } 33 | 34 | #pragma mark - RunCallback 35 | 36 | - (void)richTextRunDealloc 37 | { 38 | 39 | } 40 | 41 | - (CGFloat)richTextRunGetAscent 42 | { 43 | return self.font.ascender; 44 | } 45 | 46 | - (CGFloat)richTextRunGetDescent 47 | { 48 | return self.font.descender; 49 | } 50 | 51 | - (CGFloat)richTextRunGetWidth 52 | { 53 | return self.font.ascender - self.font.descender; 54 | } 55 | 56 | #pragma mark - RunDelegateCallback 57 | 58 | void TQRichTextRunDelegateDeallocCallback(void *refCon) 59 | { 60 | TQRichTextRunDelegate *run =(__bridge TQRichTextRunDelegate *) refCon; 61 | 62 | [run richTextRunDealloc]; 63 | } 64 | 65 | //--上行高度 66 | CGFloat TQRichTextRunDelegateGetAscentCallback(void *refCon) 67 | { 68 | TQRichTextRunDelegate *run =(__bridge TQRichTextRunDelegate *) refCon; 69 | 70 | return [run richTextRunGetAscent]; 71 | } 72 | 73 | //--下行高度 74 | CGFloat TQRichTextRunDelegateGetDescentCallback(void *refCon) 75 | { 76 | TQRichTextRunDelegate *run =(__bridge TQRichTextRunDelegate *) refCon; 77 | 78 | return [run richTextRunGetDescent]; 79 | } 80 | 81 | //-- 宽 82 | CGFloat TQRichTextRunDelegateGetWidthCallback(void *refCon) 83 | { 84 | TQRichTextRunDelegate *run =(__bridge TQRichTextRunDelegate *) refCon; 85 | 86 | return [run richTextRunGetWidth]; 87 | } 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextView/TQRichTextRunEmoji.h: -------------------------------------------------------------------------------- 1 | // 2 | // TQRichTextRunEmoji.h 3 | // TQRichTextViewDemo 4 | // 5 | // Created by fuqiang on 2/28/14. 6 | // Copyright (c) 2014 fuqiang. All rights reserved. 7 | // 8 | 9 | #import "TQRichTextRunDelegate.h" 10 | 11 | @interface TQRichTextRunEmoji : TQRichTextRunDelegate 12 | 13 | /** 14 | * 解析字符串中url内容生成Run对象 15 | * 16 | * @param attributedString 内容 17 | * 18 | * @return TQRichTextRunURL对象数组 19 | */ 20 | + (NSArray *)runsForAttributedString:(NSMutableAttributedString *)attributedString; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextView/TQRichTextRunEmoji.m: -------------------------------------------------------------------------------- 1 | // 2 | // TQRichTextRunEmoji.m 3 | // TQRichTextViewDemo 4 | // 5 | // Created by fuqiang on 2/28/14. 6 | // Copyright (c) 2014 fuqiang. All rights reserved. 7 | // 8 | 9 | #import "TQRichTextRunEmoji.h" 10 | 11 | @implementation TQRichTextRunEmoji 12 | 13 | /** 14 | * 返回表情数组 15 | */ 16 | + (NSArray *) emojiStringArray 17 | { 18 | return [NSArray arrayWithObjects:@"[smile]",@"[cry]",@"[hei]",nil]; 19 | } 20 | 21 | /** 22 | * 解析字符串中url内容生成Run对象 23 | * 24 | * @param attributedString 内容 25 | * 26 | * @return TQRichTextRunURL对象数组 27 | */ 28 | + (NSArray *)runsForAttributedString:(NSMutableAttributedString *)attributedString 29 | { 30 | NSString *markL = @"["; 31 | NSString *markR = @"]"; 32 | NSString *string = attributedString.string; 33 | NSMutableArray *array = [NSMutableArray array]; 34 | NSMutableArray *stack = [[NSMutableArray alloc] init]; 35 | 36 | for (int i = 0; i < string.length; i++) 37 | { 38 | NSString *s = [string substringWithRange:NSMakeRange(i, 1)]; 39 | 40 | if (([s isEqualToString:markL]) || ((stack.count > 0) && [stack[0] isEqualToString:markL])) 41 | { 42 | if (([s isEqualToString:markL]) && ((stack.count > 0) && [stack[0] isEqualToString:markL])) 43 | { 44 | [stack removeAllObjects]; 45 | } 46 | 47 | [stack addObject:s]; 48 | 49 | if ([s isEqualToString:markR] || (i == string.length - 1)) 50 | { 51 | NSMutableString *emojiStr = [[NSMutableString alloc] init]; 52 | for (NSString *c in stack) 53 | { 54 | [emojiStr appendString:c]; 55 | } 56 | 57 | if ([[TQRichTextRunEmoji emojiStringArray] containsObject:emojiStr]) 58 | { 59 | NSRange range = NSMakeRange(i + 1 - emojiStr.length, emojiStr.length); 60 | 61 | [attributedString replaceCharactersInRange:range withString:@" "]; 62 | 63 | TQRichTextRunEmoji *run = [[TQRichTextRunEmoji alloc] init]; 64 | run.range = NSMakeRange(i + 1 - emojiStr.length, 1); 65 | run.text = emojiStr; 66 | run.drawSelf = YES; 67 | [run decorateToAttributedString:attributedString range:run.range]; 68 | 69 | [array addObject:run]; 70 | } 71 | 72 | [stack removeAllObjects]; 73 | } 74 | } 75 | } 76 | 77 | return array; 78 | } 79 | 80 | /** 81 | * 绘制Run内容 82 | */ 83 | - (void)drawRunWithRect:(CGRect)rect 84 | { 85 | CGContextRef context = UIGraphicsGetCurrentContext(); 86 | 87 | NSString *emojiString = [NSString stringWithFormat:@"%@.png",self.text]; 88 | 89 | UIImage *image = [UIImage imageNamed:emojiString]; 90 | if (image) 91 | { 92 | CGContextDrawImage(context, rect, image.CGImage); 93 | } 94 | } 95 | 96 | @end 97 | -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextView/TQRichTextRunURL.h: -------------------------------------------------------------------------------- 1 | // 2 | // TQRichTextRunURL.h 3 | // TQRichTextViewDemo 4 | // 5 | // Created by fuqiang on 2/27/14. 6 | // Copyright (c) 2014 fuqiang. All rights reserved. 7 | // 8 | 9 | #import "TQRichTextRun.h" 10 | 11 | @interface TQRichTextRunURL : TQRichTextRun 12 | 13 | /** 14 | * 解析字符串中url内容生成Run对象 15 | * 16 | * @param attributedString 内容 17 | * 18 | * @return TQRichTextRunURL对象数组 19 | */ 20 | + (NSArray *)runsForAttributedString:(NSMutableAttributedString *)attributedString; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextView/TQRichTextRunURL.m: -------------------------------------------------------------------------------- 1 | // 2 | // TQRichTextRunURL.m 3 | // TQRichTextViewDemo 4 | // 5 | // Created by fuqiang on 2/27/14. 6 | // Copyright (c) 2014 fuqiang. All rights reserved. 7 | // 8 | 9 | #import "TQRichTextRunURL.h" 10 | 11 | @implementation TQRichTextRunURL 12 | 13 | /** 14 | * 向字符串中添加相关Run类型属性 15 | */ 16 | - (void)decorateToAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range 17 | { 18 | [super decorateToAttributedString:attributedString range:range]; 19 | [attributedString addAttribute:(NSString *)kCTForegroundColorAttributeName value:(id)[UIColor blueColor].CGColor range:range]; 20 | } 21 | 22 | /** 23 | * 解析字符串中url内容生成Run对象 24 | * 25 | * @param attributedString 内容 26 | * 27 | * @return TQRichTextRunURL对象数组 28 | */ 29 | + (NSArray *)runsForAttributedString:(NSMutableAttributedString *)attributedString 30 | { 31 | NSString *string = attributedString.string; 32 | NSMutableArray *array = [NSMutableArray array]; 33 | 34 | NSError *error = nil;; 35 | NSString *regulaStr = @"((http[s]{0,1}|ftp)://[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)|(www.[a-zA-Z0-9\\.\\-]+\\.([a-zA-Z]{2,4})(:\\d+)?(/[a-zA-Z0-9\\.\\-~!@#$%^&*+?:_/=<>]*)?)"; 36 | NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regulaStr 37 | options:NSRegularExpressionCaseInsensitive 38 | error:&error]; 39 | if (error == nil) 40 | { 41 | NSArray *arrayOfAllMatches = [regex matchesInString:string 42 | options:0 43 | range:NSMakeRange(0, [string length])]; 44 | 45 | for (NSTextCheckingResult *match in arrayOfAllMatches) 46 | { 47 | NSString* substringForMatch = [string substringWithRange:match.range]; 48 | 49 | TQRichTextRunURL *run = [[TQRichTextRunURL alloc] init]; 50 | run.range = match.range; 51 | run.text = substringForMatch; 52 | run.drawSelf = NO; 53 | [run decorateToAttributedString:attributedString range:match.range]; 54 | [array addObject:run ]; 55 | } 56 | } 57 | 58 | return array; 59 | } 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextView/TQRichTextView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TQRichTextView.h 3 | // TQRichTextViewDemo 4 | // 5 | // Created by fuqiang on 2/26/14. 6 | // Copyright (c) 2014 fuqiang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "TQRichTextRun.h" 11 | #import "TQRichTextRunURL.h" 12 | #import "TQRichTextRunEmoji.h" 13 | 14 | @class TQRichTextView; 15 | 16 | typedef NS_OPTIONS(NSUInteger, TQRichTextRunTypeList) 17 | { 18 | TQRichTextRunNoneType = 0, 19 | TQRichTextRunURLType = 1 << 0, 20 | TQRichTextRunEmojiType = 1 << 1, 21 | }; 22 | 23 | @protocol TQRichTextViewDelegate 24 | 25 | @optional 26 | - (void)richTextView:(TQRichTextView *)view touchBeginRun:(TQRichTextRun *)run; 27 | - (void)richTextView:(TQRichTextView *)view touchEndRun:(TQRichTextRun *)run; 28 | - (void)richTextView:(TQRichTextView *)view touchCanceledRun:(TQRichTextRun *)run; 29 | 30 | @end 31 | 32 | @interface TQRichTextView : UIView 33 | 34 | @property(nonatomic,weak) id delegage; 35 | 36 | @property (nonatomic,copy ) NSString *text; // default is nil 37 | @property (nonatomic,copy ) NSMutableAttributedString *attributedText; 38 | @property (nonatomic,strong) UIFont *font; // default is nil (system font 17 plain) 39 | @property (nonatomic,strong) UIColor *textColor; // default is nil (text draws black) 40 | @property (nonatomic ) TQRichTextRunTypeList runTypeList; 41 | @property (nonatomic) CGFloat lineSpace; 42 | 43 | + (NSMutableAttributedString *)createAttributedStringWithText:(NSString *)text font:(UIFont *)font lineSpace:(CGFloat)lineSpace; 44 | 45 | + (NSArray *)createTextRunsWithAttString:(NSMutableAttributedString *)attString runTypeList:(TQRichTextRunTypeList)typeList; 46 | 47 | + (CGRect)boundingRectWithSize:(CGSize)size font:(UIFont *)font AttString:(NSMutableAttributedString *)attString; 48 | 49 | + (CGRect)boundingRectWithSize:(CGSize)size font:(UIFont *)font string:(NSString *)string lineSpace:(CGFloat )lineSpace; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextView/TQRichTextView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TQRichTextView.m 3 | // TQRichTextViewDemo 4 | // 5 | // Created by fuqiang on 2/26/14. 6 | // Copyright (c) 2014 fuqiang. All rights reserved. 7 | // 8 | 9 | #import "TQRichTextView.h" 10 | #import 11 | 12 | 13 | @interface TQRichTextView () 14 | 15 | @property (nonatomic,strong) NSMutableArray *runs; 16 | @property (nonatomic,strong) NSMutableDictionary *runRectDictionary; 17 | @property (nonatomic,strong) TQRichTextRun *touchRun; 18 | 19 | @end 20 | 21 | @implementation TQRichTextView 22 | 23 | - (id)init 24 | { 25 | self = [super init]; 26 | if (self) { 27 | [self createDefault]; 28 | } 29 | return self; 30 | } 31 | 32 | - (id)initWithFrame:(CGRect)frame 33 | { 34 | self = [super initWithFrame:frame]; 35 | if (self) { 36 | [self createDefault]; 37 | } 38 | return self; 39 | } 40 | 41 | #pragma mark - CreateDefault 42 | 43 | - (void)createDefault 44 | { 45 | //public 46 | self.text = nil; 47 | self.font = [UIFont systemFontOfSize:17.0f]; 48 | self.textColor = [UIColor blackColor]; 49 | self.runTypeList = TQRichTextRunURLType | TQRichTextRunEmojiType; 50 | self.lineSpace = 2.0f; 51 | self.attributedText = nil; 52 | //private 53 | self.runs = [NSMutableArray array]; 54 | self.runRectDictionary = [NSMutableDictionary dictionary]; 55 | self.touchRun = nil; 56 | } 57 | 58 | #pragma mark - Draw Rect 59 | 60 | - (void)drawRect:(CGRect)rect 61 | { 62 | if (self.text == nil){ 63 | return; 64 | } 65 | 66 | [self.runs removeAllObjects]; 67 | [self.runRectDictionary removeAllObjects]; 68 | 69 | CGRect viewRect = self.bounds; 70 | 71 | //绘制的文本 72 | NSMutableAttributedString *attString = nil; 73 | 74 | if (self.attributedText == nil) 75 | { 76 | attString = [[self class] createAttributedStringWithText:self.text font:self.font lineSpace:self.lineSpace]; 77 | } 78 | else 79 | { 80 | attString = self.attributedText; 81 | } 82 | 83 | NSArray *runs = [[self class] createTextRunsWithAttString:attString runTypeList:self.runTypeList]; 84 | 85 | [self.runs addObjectsFromArray:runs]; 86 | 87 | //绘图上下文 88 | CGContextRef contextRef = UIGraphicsGetCurrentContext(); 89 | 90 | //修正坐标系 91 | CGAffineTransform affineTransform = CGAffineTransformIdentity; 92 | affineTransform = CGAffineTransformMakeTranslation(0.0, viewRect.size.height); 93 | affineTransform = CGAffineTransformScale(affineTransform, 1.0, -1.0); 94 | CGContextConcatCTM(contextRef, affineTransform); 95 | 96 | //创建一个用来描画文字的路径,其区域为当前视图的bounds CGPath 97 | CGMutablePathRef pathRef = CGPathCreateMutable(); 98 | CGPathAddRect(pathRef, NULL, viewRect); 99 | 100 | //创建一个framesetter用来管理描画文字的frame CTFramesetter 101 | CTFramesetterRef framesetterRef = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attString); 102 | 103 | //创建由framesetter管理的frame,是描画文字的一个视图范围 CTFrame 104 | CTFrameRef frameRef = CTFramesetterCreateFrame(framesetterRef, CFRangeMake(0, 0), pathRef, nil); 105 | 106 | CFArrayRef lines = CTFrameGetLines(frameRef); 107 | 108 | CGPoint lineOrigins[CFArrayGetCount(lines)]; 109 | CTFrameGetLineOrigins(frameRef, CFRangeMake(0, 0), lineOrigins); 110 | 111 | for (int i = 0; i < CFArrayGetCount(lines); i++) 112 | { 113 | CTLineRef lineRef= CFArrayGetValueAtIndex(lines, i); 114 | CGFloat lineAscent; 115 | CGFloat lineDescent; 116 | CGFloat lineLeading; 117 | CGPoint lineOrigin = lineOrigins[i]; 118 | CTLineGetTypographicBounds(lineRef, &lineAscent, &lineDescent, &lineLeading); 119 | CFArrayRef runs = CTLineGetGlyphRuns(lineRef); 120 | 121 | for (int j = 0; j < CFArrayGetCount(runs); j++) 122 | { 123 | CTRunRef runRef = CFArrayGetValueAtIndex(runs, j); 124 | CGFloat runAscent; 125 | CGFloat runDescent; 126 | CGRect runRect; 127 | 128 | runRect.size.width = CTRunGetTypographicBounds(runRef, CFRangeMake(0,0), &runAscent, &runDescent, NULL); 129 | runRect = CGRectMake(lineOrigin.x + CTLineGetOffsetForStringIndex(lineRef, CTRunGetStringRange(runRef).location, NULL), 130 | lineOrigin.y , 131 | runRect.size.width, 132 | runAscent + runDescent); 133 | 134 | NSDictionary * attributes = (__bridge NSDictionary *)CTRunGetAttributes(runRef); 135 | TQRichTextRun *richTextRun = [attributes objectForKey:kTQRichTextRunSelfAttributedName]; 136 | 137 | if (richTextRun.drawSelf) 138 | { 139 | CGFloat runAscent,runDescent; 140 | CGFloat runWidth = CTRunGetTypographicBounds(runRef, CFRangeMake(0,0), &runAscent, &runDescent, NULL); 141 | CGFloat runHeight = (lineAscent + lineDescent ); 142 | CGFloat runPointX = runRect.origin.x + lineOrigin.x; 143 | CGFloat runPointY = lineOrigin.y ; 144 | 145 | CGRect runRectDraw = CGRectMake(runPointX, runPointY, runWidth, runHeight); 146 | 147 | [richTextRun drawRunWithRect:runRectDraw]; 148 | 149 | [self.runRectDictionary setObject:richTextRun forKey:[NSValue valueWithCGRect:runRectDraw]]; 150 | } 151 | else 152 | { 153 | if (richTextRun) 154 | { 155 | [self.runRectDictionary setObject:richTextRun forKey:[NSValue valueWithCGRect:runRect]]; 156 | } 157 | } 158 | } 159 | } 160 | 161 | //通过context在frame中描画文字内容 162 | CTFrameDraw(frameRef, contextRef); 163 | 164 | CFRelease(pathRef); 165 | CFRelease(frameRef); 166 | CFRelease(framesetterRef); 167 | } 168 | 169 | #pragma mark - Set 170 | - (void)setText:(NSString *)text 171 | { 172 | [self setNeedsDisplay]; 173 | _text = text; 174 | } 175 | 176 | - (void)setFont:(UIFont *)font 177 | { 178 | [self setNeedsDisplay]; 179 | _font = font; 180 | } 181 | 182 | - (void)setTextColor:(UIColor *)textColor 183 | { 184 | [self setNeedsDisplay]; 185 | _textColor = textColor; 186 | } 187 | 188 | - (void)setLineSpace:(CGFloat)lineSpace 189 | { 190 | [self setNeedsDisplay]; 191 | _lineSpace = lineSpace; 192 | 193 | } 194 | 195 | #pragma mark - Touches 196 | 197 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 198 | { 199 | [super touchesBegan:touches withEvent:event]; 200 | 201 | CGPoint location = [(UITouch *)[touches anyObject] locationInView:self]; 202 | CGPoint runLocation = CGPointMake(location.x, self.frame.size.height - location.y); 203 | 204 | if (self.delegage && [self.delegage respondsToSelector:@selector(richTextView: touchBeginRun:)]) 205 | { 206 | __weak TQRichTextView *weakSelf = self; 207 | 208 | [self.runRectDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){ 209 | 210 | CGRect rect = [((NSValue *)key) CGRectValue]; 211 | if(CGRectContainsPoint(rect, runLocation)) 212 | { 213 | self.touchRun = obj; 214 | [weakSelf.delegage richTextView:weakSelf touchBeginRun:obj]; 215 | } 216 | }]; 217 | } 218 | } 219 | 220 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 221 | { 222 | [super touchesMoved:touches withEvent:event]; 223 | } 224 | 225 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 226 | { 227 | [super touchesEnded:touches withEvent:event]; 228 | 229 | CGPoint location = [(UITouch *)[touches anyObject] locationInView:self]; 230 | CGPoint runLocation = CGPointMake(location.x, self.frame.size.height - location.y); 231 | 232 | if (self.delegage && [self.delegage respondsToSelector:@selector(richTextView: touchEndRun:)]) 233 | { 234 | __weak TQRichTextView *weakSelf = self; 235 | 236 | [self.runRectDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){ 237 | 238 | CGRect rect = [((NSValue *)key) CGRectValue]; 239 | if(CGRectContainsPoint(rect, runLocation)) 240 | { 241 | self.touchRun = obj; 242 | [weakSelf.delegage richTextView:weakSelf touchEndRun:obj]; 243 | } 244 | }]; 245 | } 246 | } 247 | 248 | - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event 249 | { 250 | [super touchesCancelled:touches withEvent:event]; 251 | 252 | CGPoint location = [(UITouch *)[touches anyObject] locationInView:self]; 253 | CGPoint runLocation = CGPointMake(location.x, self.frame.size.height - location.y); 254 | 255 | if (self.delegage && [self.delegage respondsToSelector:@selector(richTextView: touchCanceledRun:)]) 256 | { 257 | __weak TQRichTextView *weakSelf = self; 258 | 259 | [self.runRectDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){ 260 | 261 | CGRect rect = [((NSValue *)key) CGRectValue]; 262 | if(CGRectContainsPoint(rect, runLocation)) 263 | { 264 | self.touchRun = obj; 265 | [weakSelf.delegage richTextView:weakSelf touchCanceledRun:obj]; 266 | } 267 | }]; 268 | } 269 | } 270 | 271 | - (UIResponder*)nextResponder 272 | { 273 | [super nextResponder]; 274 | 275 | return self.touchRun; 276 | } 277 | 278 | #pragma mark - 279 | 280 | + (NSMutableAttributedString *)createAttributedStringWithText:(NSString *)text font:(UIFont *)font lineSpace:(CGFloat)lineSpace 281 | { 282 | NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:text]; 283 | 284 | //设置字体 285 | CTFontRef fontRef = CTFontCreateWithName((__bridge CFStringRef)font.fontName, font.pointSize, NULL); 286 | [attString addAttribute:(NSString*)kCTFontAttributeName value:(__bridge id)fontRef range:NSMakeRange(0,attString.length)]; 287 | CFRelease(fontRef); 288 | 289 | //添加换行模式 290 | CTParagraphStyleSetting lineBreakMode; 291 | CTLineBreakMode lineBreak = kCTLineBreakByCharWrapping; 292 | lineBreakMode.spec = kCTParagraphStyleSpecifierLineBreakMode; 293 | lineBreakMode.value = &lineBreak; 294 | lineBreakMode.valueSize = sizeof(lineBreak); 295 | 296 | //行距 297 | CTParagraphStyleSetting lineSpaceStyle; 298 | lineSpaceStyle.spec = kCTParagraphStyleSpecifierLineSpacing; 299 | lineSpaceStyle.valueSize = sizeof(lineSpace); 300 | lineSpaceStyle.value =&lineSpace; 301 | 302 | CTParagraphStyleSetting settings[] = {lineSpaceStyle,lineBreakMode}; 303 | CTParagraphStyleRef style = CTParagraphStyleCreate(settings, sizeof(settings)/sizeof(settings[0])); 304 | NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithObject:(__bridge id)style forKey:(id)kCTParagraphStyleAttributeName ]; 305 | CFRelease(style); 306 | 307 | [attString addAttributes:attributes range:NSMakeRange(0, [attString length])]; 308 | 309 | return attString; 310 | } 311 | 312 | + (NSArray *)createTextRunsWithAttString:(NSMutableAttributedString *)attString runTypeList:(TQRichTextRunTypeList)typeList 313 | { 314 | NSMutableArray *array = [[NSMutableArray alloc] init]; 315 | 316 | if (typeList != TQRichTextRunNoneType) 317 | { 318 | if ((typeList & TQRichTextRunURLType) == TQRichTextRunURLType) 319 | { 320 | [array addObjectsFromArray:[TQRichTextRunURL runsForAttributedString:attString]]; 321 | } 322 | 323 | if ((typeList & TQRichTextRunEmojiType) == TQRichTextRunEmojiType) 324 | { 325 | [array addObjectsFromArray:[TQRichTextRunEmoji runsForAttributedString:attString]]; 326 | } 327 | } 328 | 329 | return array; 330 | } 331 | 332 | + (CGRect)boundingRectWithSize:(CGSize)size font:(UIFont *)font AttString:(NSMutableAttributedString *)attString 333 | { 334 | [[self class] createTextRunsWithAttString:attString runTypeList:TQRichTextRunURLType | TQRichTextRunEmojiType ]; 335 | 336 | NSDictionary *dic = [attString attributesAtIndex:0 effectiveRange:nil]; 337 | CTParagraphStyleRef paragraphStyle = (__bridge CTParagraphStyleRef)[dic objectForKey:(id)kCTParagraphStyleAttributeName]; 338 | CGFloat linespace = 0; 339 | 340 | CTParagraphStyleGetValueForSpecifier(paragraphStyle, kCTParagraphStyleSpecifierLineSpacing, sizeof(linespace), &linespace); 341 | 342 | CGFloat height = 0; 343 | CGFloat width = 0; 344 | CFIndex lineIndex = 0; 345 | 346 | CGMutablePathRef pathRef = CGPathCreateMutable(); 347 | CGPathAddRect(pathRef, NULL, CGRectMake(0, 0, size.width, size.height)); 348 | CTFramesetterRef framesetterRef = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)attString); 349 | CTFrameRef frameRef = CTFramesetterCreateFrame(framesetterRef, CFRangeMake(0, 0), pathRef, nil); 350 | CFArrayRef lines = CTFrameGetLines(frameRef); 351 | 352 | lineIndex = CFArrayGetCount(lines); 353 | 354 | if (lineIndex > 1) 355 | { 356 | for (int i = 0; i 10 | 11 | @interface TQAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @property (strong, nonatomic) UIViewController *viewController; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextViewDemo/TQAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // TQAppDelegate.m 3 | // TQRichTextViewDemo 4 | // 5 | // Created by fuqiang on 2/26/14. 6 | // Copyright (c) 2014 fuqiang. All rights reserved. 7 | // 8 | 9 | #import "TQAppDelegate.h" 10 | #import "TQViewController.h" 11 | #import "TQViewController.h" 12 | @implementation TQAppDelegate 13 | 14 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 15 | { 16 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 17 | self.viewController = [[TQViewController alloc] init]; 18 | self.window.rootViewController = self.viewController; 19 | [self.window makeKeyAndVisible]; 20 | return YES; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextViewDemo/TQRichTextViewDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | TinyQ.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextViewDemo/TQRichTextViewDemo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextViewDemo/TQViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TQViewController.h 3 | // TQRichTextViewDemo 4 | // 5 | // Created by fuqiang on 2/26/14. 6 | // Copyright (c) 2014 fuqiang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "TQRichTextView.h" 11 | 12 | @interface TQViewController : UIViewController 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextViewDemo/TQViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // TQViewController.m 3 | // TQRichTextViewDemo 4 | // 5 | // Created by fuqiang on 2/26/14. 6 | // Copyright (c) 2014 fuqiang. All rights reserved. 7 | // 8 | 9 | #import "TQViewController.h" 10 | 11 | 12 | @interface TQViewController () 13 | 14 | @end 15 | 16 | @implementation TQViewController 17 | 18 | - (void)viewDidLoad 19 | { 20 | [super viewDidLoad]; 21 | 22 | self.view .backgroundColor = [UIColor whiteColor]; 23 | NSString *string = @"[smile]RichTextBox控件允许用户输入和编辑文本的同时提供了[cry]比普通的TextBox控件更高级的格式特征。 RichTextBox控件提供了数个有用的特征[smile],你可以在控件中安排文本的格式。[smile]要改变文本的格式,[cry]必须先选中该文本。只有选中的文本才可以编排字符和段落的格式。https://github.com额/有了这些属性,就可以设置[cry]文本使用粗体,改变[cry]字体的颜色,创建超底稿和子底稿。[smile]也可以设置左右缩排或不缩排,从而调整段落的格式。 RichTextBox控件可以打开和保存RTF文件或普通的ASCII文本文件。你可以使用控件的方法(LoadFile[smile]SaveFile)直接读和写文件 RichTextBox控件使用集合支https://github.com持嵌入的对象。每个嵌入控件中的对象都表示为一个[1]对象。这允许文档中创建的控件可以包含其他控件或文档。https://github.com/ 例如,可以创建一个包含报表、Microsoft Word文档或任何在系统中注册的其他OLE对象的文档。要在RichTextBox控件中插入对象,可以简单地拖住一个文件(如使用Windows 95的Explorerwww.chiphell.com/或其他应用程序(如Microsoft Word)中所用文件的加亮部分(选择部分),将其直接放到该RichTextBox控件上。http://www.cnblogs.com/CCSSPP/"; 24 | 25 | CGRect rect = [TQRichTextView boundingRectWithSize:CGSizeMake(320, 500) font:[UIFont systemFontOfSize:13] string:string lineSpace:1.0f]; 26 | 27 | NSLog(@"%@",NSStringFromCGRect(rect)); 28 | 29 | TQRichTextView *textView = [[TQRichTextView alloc] initWithFrame:CGRectMake(0, 20, rect.size.width, rect.size.height)]; 30 | textView.text = string; 31 | textView.lineSpace = 1.0f; 32 | textView.font = [UIFont systemFontOfSize:13.0f]; 33 | textView.backgroundColor = [UIColor grayColor]; 34 | textView.delegage = self; 35 | 36 | [self.view addSubview:textView]; 37 | 38 | } 39 | 40 | - (void)richTextView:(TQRichTextView *)view touchBeginRun:(TQRichTextRun *)run 41 | { 42 | 43 | } 44 | 45 | - (void)richTextView:(TQRichTextView *)view touchEndRun:(TQRichTextRun *)run 46 | { 47 | if ([run isKindOfClass:[TQRichTextRunURL class]]) 48 | { 49 | [[UIApplication sharedApplication] openURL:[NSURL URLWithString:run.text]]; 50 | } 51 | 52 | NSLog(@"%@",run.text); 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextViewDemo/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextViewDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TQRichTextViewDemo 4 | // 5 | // Created by fuqiang on 2/26/14. 6 | // Copyright (c) 2014 fuqiang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TQAppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([TQAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextViewDemoTests/TQRichTextViewDemoTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | TinyQ.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextViewDemoTests/TQRichTextViewDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TQRichTextViewDemoTests.m 3 | // TQRichTextViewDemoTests 4 | // 5 | // Created by fuqiang on 2/26/14. 6 | // Copyright (c) 2014 fuqiang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TQRichTextViewDemoTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation TQRichTextViewDemoTests 16 | 17 | - (void)setUp 18 | { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown 24 | { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | [super tearDown]; 27 | } 28 | 29 | - (void)testExample 30 | { 31 | XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /TQRichTextViewDemo/TQRichTextViewDemoTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | --------------------------------------------------------------------------------