CoreText实现图文混排之文字环绕及点击算法
系列文章:
终于我来完成我CoreText图文混排的最后一章了。
先说一下我为什么会来补发这一章呢?
1.老司机最开始没有留demo,以至于这个博客老司机从发出来到现在整整维护了半年了=。=其实博客里面就是全部代码,但是宝宝们任性的要demo。
2.时间长了,阅读量也上去了,老司机觉得自己有必要对粉丝们负责了
3.有很多同学询问是否能做出文字环绕的效果,老司机之前的确也没有写过,这一篇是要补上的。
4.关于点击事件,老司机在第二篇文章中有提到过一个思路,是每次遍历所有CTRun去做的。后期老司机考虑到遍历的实现效率似乎有些低,所以老司机研究了一下,重新整理思路,优化了一下算法。
基于以上原因,以及一个阴谋
,老司机又来更文了。
劳资回来了
在这篇文章中你可以看到以下内容:
图片环绕的实现方式
点击事件获取的优化算法
看了本篇博客,老司机能够帮你实现如下效果
CoreText
这篇博客是以前两篇博客作为知识铺垫的,如果没有看过前两篇博客的童靴建议你去补票。当然本身你就了解CoreText相关知识的话也可以直接看本篇文章。
全部代码
优化算法以后,代码有些许改变,不过主体思路是一致的。下面是全部代码。
@interface CoreTextV (){
CTFrameRef _frame;
NSInteger _length;
CGRect _imgFrm;
NSMutableArray * arrText;
}
@end
@implementation CoreTextV
-(void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
arrText = [NSMutableArray array];
NSMutableAttributedString * attributeStr = [[NSMutableAttributedString alloc] initWithString:@"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"];
[attributeStr addAttribute:NSForegroundColorAttributeName value:[UIColor whiteColor] range:NSMakeRange(0, attributeStr.length)];
CTRunDelegateCallbacks callBacks;
memset(&callBacks, 0, sizeof(CTRunDelegateCallbacks));
callBacks.version = kCTRunDelegateVersion1;
callBacks.getAscent = ascentCallBacks;
callBacks.getDescent = descentCallBacks;
callBacks.getWidth = widthCallBacks;
NSDictionary * dicPic = @{@"height":@90,@"width":@160};
CTRunDelegateRef delegate = CTRunDelegateCreate(& callBacks, (__bridge void *)dicPic); unichar placeHolder = 0xFFFC; NSString * placeHolderStr = [NSString stringWithCharacters:&placeHolder length:1]; NSMutableAttributedString * placeHolderAttrStr = [[NSMutableAttributedString alloc] initWithString:placeHolderStr]; CFAttributedStringSetAttribute((CFMutableAttributedStringRef)placeHolderAttrStr, CFRangeMake(0, 1), kCTRunDelegateAttributeName, delegate); CFRelease(delegate);
[attributeStr insertAttributedString:placeHolderAttrStr atIndex:300]; NSDictionary * activeAttr = @{NSForegroundColorAttributeName:[UIColor redColor],@"click":NSStringFromSelector(@selector(click))};
[attributeStr addAttributes:activeAttr range:NSMakeRange(100, 30)];
[attributeStr addAttributes:activeAttr range:NSMakeRange(400, 100)];
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributeStr); UIBezierPath * path = [UIBezierPath bezierPathWithRect:self.bounds]; UIBezierPath * cirP = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 100, 100, 200)];
[path appendPath:cirP];
_length = attributeStr.length;
_frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, _length), path.CGPath, NULL);
CTFrameDraw(_frame, context);
UIImage * image = [UIImage imageNamed:@"1.jpg"];
[self handleActiveRectWithFrame:_frame];
CGContextDrawImage(context,_imgFrm, image.CGImage);
CGContextDrawImage(context, cirP.bounds, [[UIImage imageNamed:@"1.jpg"] dw_ClipImageWithPath:cirP mode:(DWContentModeScaleAspectFill)].CGImage); CFRelease(_frame); CFRelease(frameSetter); }
static CGFloat ascentCallBacks(void * ref) {
return [(NSNumber *)[(__bridge NSDictionary *)ref valueForKey:@"height"] floatValue];
}
static CGFloat descentCallBacks(void * ref) {
return 0;
}
static CGFloat widthCallBacks(void * ref) {
return [(NSNumber *)[(__bridge NSDictionary *)ref valueForKey:@"width"] floatValue];
}
-(void)handleActiveRectWithFrame:(CTFrameRef)frame {
NSArray * arrLines = (NSArray *)CTFrameGetLines(frame);
NSInteger count = [arrLines count]; CGPoint points[count];
CTFrameGetLineOrigins(frame, CFRangeMake(0, 0), points);
for (int i = 0; i < count; i ++) {
CTLineRef line = (__bridge CTLineRef)arrLines[i];
NSArray * arrGlyphRun = (NSArray *)CTLineGetGlyphRuns(line);
for (int j = 0; j < arrGlyphRun.count; j ++) {
CTRunRef run = (__bridge CTRunRef)arrGlyphRun[j];
NSDictionary * attributes = (NSDictionary *)CTRunGetAttributes(run);
CTRunDelegateRef delegate = (__bridge CTRunDelegateRef)[attributes valueForKey:(id)kCTRunDelegateAttributeName]; CGPoint point = points[i]; if (delegate == nil) { NSString * string = attributes[@"click"]; if (string) {
[arrText addObject:[NSValue valueWithCGRect:[self getLocWithFrame:frame CTLine:line CTRun:run origin:point]]];
} continue;
}
NSDictionary * metaDic = CTRunDelegateGetRefCon(delegate);
if (![metaDic isKindOfClass:[NSDictionary class]]) {
continue; }
_imgFrm = [self getLocWithFrame:frame CTLine:line CTRun:run origin:point];
}
}
}
-(CGRect)getLocWithFrame:(CTFrameRef)frame CTLine:(CTLineRef)line CTRun:(CTRunRef)run origin:(CGPoint)origin {
CGFloat ascent;
CGFloat descent;
CGRect boundsRun;
boundsRun.size.width = CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent, NULL);
boundsRun.size.height = ascent + descent;
CGFloat xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, NULL);
boundsRun.origin.x = origin.x + xOffset;
boundsRun.origin.y = origin.y - descent;
CGPathRef path = CTFrameGetPath(frame);
CGRect colRect = CGPathGetBoundingBox(path);
CGRect deleteBounds = CGRectOffset(boundsRun, colRect.origin.x, colRect.origin.y);
return deleteBounds;
}
-(CGRect)convertRectFromLoc:(CGRect)rect {
return CGRectMake(rect.origin.x, self.bounds.size.height - rect.origin.y - rect.size.height, rect.size.width, rect.size.height);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
UITouch * touch = [touches anyObject];
CGPoint location = [touch locationInView:self];
CGRect imageFrmToScreen = [self convertRectFromLoc:_imgFrm];
if (CGRectContainsPoint(imageFrmToScreen, location)) {
[[[UIAlertView alloc] initWithTitle:nil message:@"你点击了图片" delegate:nil cancelButtonTitle:@"好的" otherButtonTitles:nil] show];
return;
}
[arrText enumerateObjectsUsingBlock:^(NSValue * rectV, NSUInteger idx, BOOL * _Nonnull stop) { CGRect textFrmToScreen = [self convertRectFromLoc:[rectV CGRectValue]];
if (CGRectContainsPoint(textFrmToScreen, location)) {
[self click];
*stop = YES;
}
}];
}
-(void)click {
[[[UIAlertView alloc] initWithTitle:nil message:@"你点击了文字" delegate:nil cancelButtonTitle:@"好的" otherButtonTitles:nil] show];
}
@end
只关心结果或者着急写项目的童靴看到这里就足够了,因为所有代码都在,想找demo的话就去文章末尾找吧。因为接下来老司机要开始扯淡了
。。。跟你们讲讲一切的实现思路
。
图片环绕的实现方式
由于我只是给个demo,所以一切代码均从简写。实际过程中,代码应进行封装分块。
我们将视线集中到drawRect
方法中吧。
之前的文章老司机讲过,我们在drawRect中绘制文本的时候主要是根据Path去绘制
的。
UIBezierPath * path = [UIBezierPath bezierPathWithRect:self.bounds];
UIBezierPath * cirP = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 100, 100, 200)];
[path appendPath:cirP];
_length = attributeStr.length;
_frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, _length), path.CGPath, NULL);
CTFrameDraw(_frame, context);
我们可以看到,我们是以path和frameSetter去生成我们绘制文本的frame的。所以说,只要在这个地方我们传入的path中将特殊区域排除
我们获得的frame就不包含该区域
,从而绘制的文本也不会在该区域中绘制
。
所以说上述的代码你看到的应该是这样子的文字区域
排除文字区域
这里你可能会有个疑问,问什么我cirP的rect是CGRectMake(100, 100, 100, 200),这个排除的区域却在那里。这里你还记得老司机在第一篇文章里就说过屏幕坐标系统跟系统坐标系统的区别
呢,原因就在这。
也就是说,到了这里,我们只要绘制出这个椭圆形的图片就可以了。这你可能需要借助老司机之前写好的工具类,在这个仓库里的DWImageUtils就是了。如果好用记得给我个star
吧。
有了这个工具类,你就可以这样生成椭圆图片
了
[image dw_ClipImageWithPath:cirP mode:(DWContentModeScaleAspectFill)]
有了图片了,情况基本就变成了我们熟悉的状况了,绘制图片
CGContextDrawImage(context, cirP.bounds, [[UIImage imageNamed:@"1.jpg"] dw_ClipImageWithPath:cirP mode:(DWContentModeScaleAspectFill)].CGImage);
至此,我们就绘制出环绕的文本了。也算真正的实现所谓的图文混排了。
点击事件获取的优化算法
首先老司机来讲一下目前老司机了解到的几种获取点击事件的方式。
主流方式:CTLineGetStringIndexForPosition
主流方式就是当前大部分基于CoreText封装的富文本展示类(包括TTTAttributedLabel
、NIAttributedLabel
和FTCoreTextView
)中使用的方法 CTLineGetStringIndexForPosition
。这个方法是获取当前点在所在文字处于当前绘制文本的索引值
。事实上如果没有一些其他因素的话,能使用这个方法是最简便快捷的。然而老司机为什么没有使用这个方法去获取点击事件呢?请看下面的动图
CoreText实现图文混排之文字环绕及点击算法的更多相关文章
- 【iOS】使用CoreText实现图文混排
iOS没有现成的支持图文混排的控件,而要用多个基础控件组合拼成图文混排这样复杂的排版,是件很苦逼的事情.对此的解决方案有使用CoreText进行绘制,或者使用TextKit.本文主要讲解对于CoreT ...
- CoreText实现图文混排之点击事件-b
CoreText实现图文混排之点击事件 主要思路 我们知道,CoreText是基于UIView去绘制的,那么既然有UIView,就有 -(void)touchesBegan:(NSSet<UIT ...
- CoreText 实现图文混排
CoreText 实现图文混排 相关博文推荐 IOS CoreText.framework - 基本用法 IOS CoreText.framework - 段落样子CTParagraphStyle h ...
- CoreText实现图文混排之点击事件
今天呢,我们继续把CoreText图文混排的点击事件补充上,这样我们的图文混排也算是圆满了. 哦,上一篇的链接在这里 http://www.jianshu.com/p/6db3289fb05d Cor ...
- CoreText实现图文混排
CoreText的介绍 Core Text 是基于 iOS 3.2+ 和 OSX 10.5+ 的一种能够对文本格式和文本布局进行精细控制的文本引擎.它良好的结合了 UIKit 和 Core Graph ...
- Coretext实现图文混排及Gif图片播放
CoreText是iOS3.2推出的一套文字排版和渲染框架,可以实现图文混排,富文本显示等效果. CoreText中的几个重要的概念: CTFont CTFontCollection CTFontD ...
- Unity插件之NGUI学习(5)—— 创建Label图文混排及文字点击
创建一个新的Scene,并按 Unity插件之NGUI学习(2)创建UI Root. 准备工作,制作Font.如今Project窗体创建一个Font目录.然后从系统自带字体目录中选择自己须要的字体,我 ...
- 前端知识复习:Html DIV 图文混排(文字放在图片下边)
Html知识复习之图文混排 练习练习基础 先上效果图: 废话不多说,直接贴代码: <!DOCTYPE html> <html xmlns="http://www.w3.or ...
- 图文混排--CoreText的简单运用
常见的在一些微博微信中可以看见一段文字中有不同的字体,字体有不同的颜色,并且可能会有一些笑脸之类的表情,这些可以通过图文混排做到. 图文混排可以通过WebView和CoreText做到,其他还有别的方 ...
随机推荐
- Spark源码分析
名词解释 RDD全称为ResilientDistributedDataset,弹性分布式数据集.就是分布在集群节点上的数据集,这些集合可以用来进行各种操作.最重要的一点是,某个操作计算后的数据集可以缓 ...
- SQL Server基础知识三十三问 (15-21)
15. 存储过程可以调用自己么, 或者说可能有递归的存储过程么? SP nesting最多可以到多少层? 答: 可以的. 因为Transact-SQL 支持递归, 你可以编写可以调用自己的存储过程. ...
- 【Android 百度地图实战】2.几种地图图层的显示
具体代码官网API已提供,地址在这. 效果图如下: 主要代码: // 创建选项菜单 @Override public boolean onCreateOptionsMenu(Menu menu) { ...
- Android Wi-Fi Peer-to-Peer(Android的Wi-Fi P2P对等网络)
Wi-Fi peer-to-peer(P2P,对等网络),它同意具备对应硬件的Android 4.0(API level 14)或者更高版本号的设备能够直接通过wifi而不须要其他中间中转节点就能直接 ...
- 在windows资源管理器添加进入当前目录dos窗口的快捷菜单
regedit.exe进入注册表 定位到HKEY_CLASSES_ROOT\Folder\shell 新建一项cmd,在cmd下再新建一项command,修改command的值为cmd.exe /k ...
- T-SQL 之 事务
事务全部是关于原子性的.原子性是指可以把一些事情当做一个单元来看待.从数据库的角度看,它是指应全部执行或全部都不执行的一条或多条语句的最小组合. 事务要有非常明确的开始和结束点.SQL Server中 ...
- Java高并发syncronized深入理解
1.Synchronized的作用: 能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果. 2.地位: 1)Synchronized是java的关键字,并java的怨言原生支持: ...
- VIM经常使用操作
VIM使用 移动命令 按键 说明 h 左 l 右(小写L) j 下 k 上 w 移动到下一个单词 b 移动到上一个单词 进入插入模式 命令 说明 i 在当前光标处进行编辑 I 在行首插入 A 在行末插 ...
- java统计abacbacdadbc中的每个字母出现的次数,输出格式是:a(4)b(3)c(3)d(2)
import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.TreeMap; /* ...
- 如何自定义oauthauthorizationserverprovider错误信息?
We are using the OAuthAuthorizationServerProvider class to do authorization in our ASP.NET Web Api a ...