使用 CoreText 和触摸创建可点击的操作
我在视图中有一些代码,它使用 CoreText 绘制一些属性文本。在此,我正在搜索网址并将其设置为蓝色。我们的想法是不要仅仅为了获得可点击的链接而引入 UIWebView
的所有开销。一旦用户点击该链接(而不是整个表视图单元格),我想触发一个委托方法,然后该方法将用于呈现一个模态视图,其中包含指向该 url 的 Web 视图。
我将路径和字符串本身保存为视图的实例变量,并且绘图代码发生在 -drawRect:
中(为了简洁起见,我将其省略)。
然而,我的触摸处理程序虽然不完整,但没有打印出我期望的内容。它如下:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
CGContextRef context = UIGraphicsGetCurrentContext();
NSLog(@"attribString = %@", self.attribString);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)self.attribString);
CTFrameRef ctframe = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, self.attribString.length), attribPath, NULL);
CFArrayRef lines = CTFrameGetLines(ctframe);
for(CFIndex i = 0; i < CFArrayGetCount(lines); i++)
{
CTLineRef line = CFArrayGetValueAtIndex(lines, i);
CGRect lineBounds = CTLineGetImageBounds(line, context);
// Check if tap was on our line
if(CGRectContainsPoint(lineBounds, point))
{
NSLog(@"Tapped line");
CFArrayRef runs = CTLineGetGlyphRuns(line);
for(CFIndex j = 0; j < CFArrayGetCount(runs); j++)
{
CTRunRef run = CFArrayGetValueAtIndex(runs, j);
CFRange urlStringRange = CTRunGetStringRange(run);
CGRect runBounds = CTRunGetImageBounds(run, context, urlStringRange);
if(CGRectContainsPoint(runBounds, point))
{
NSLog(@"Tapped run");
CFIndex* buffer = malloc(sizeof(CFIndex) * urlStringRange.length);
CTRunGetStringIndices(run, urlStringRange, buffer);
// TODO: Map the glyph indices to indexes in the string & Fire the delegate
}
}
}
}
}
这不是目前最漂亮的代码,我仍在努力使其工作,所以请原谅代码质量。
我遇到的问题是,当我点击链接外部时,我期望会发生的事情发生了:没有任何内容被解雇。
但是,如果我点击链接所在的同一行,我希望打印 "Tapped line"
,但这种情况不会发生,并且我希望同时打印 "Tapped line"
code> 和 "Tapped run"
在我点击 URL 时打印。
我不确定如何进一步解决这个问题,我为解决此问题而查看的资源是 Cocoa 特定的(几乎完全不适用),或者缺乏有关此特定案例的信息。
我很乐意指向文档,其中详细说明了如何正确检测在代码上绘制的核心文本的范围内是否发生触摸,但此时,我只想解决这个问题,因此任何帮助都将是巨大的赞赏。
更新:我已将问题范围缩小到坐标问题。我已经翻转了坐标(而不是如上所示),我遇到的问题是触摸按我的预期注册,但坐标空间被翻转,而且我似乎无法将其翻转回来。
I have some code in a view which draws some attributed text using CoreText. In this, I'm searching for urls and making them blue. The idea is to not bring in all the overhead of a UIWebView
just to get clickable links. Once a user taps on that link (not the whole table view cell), I want to fire off a delegate method which will then be used to present a modal view which contains a web view going to that url.
I'm saving the path and the string itself as instance variables of the view, and the drawing code happens in -drawRect:
(I've left it out for brevity).
My touch handler however, while incomplete, is not printing what I'd expect it to. It is below:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
CGContextRef context = UIGraphicsGetCurrentContext();
NSLog(@"attribString = %@", self.attribString);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)self.attribString);
CTFrameRef ctframe = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, self.attribString.length), attribPath, NULL);
CFArrayRef lines = CTFrameGetLines(ctframe);
for(CFIndex i = 0; i < CFArrayGetCount(lines); i++)
{
CTLineRef line = CFArrayGetValueAtIndex(lines, i);
CGRect lineBounds = CTLineGetImageBounds(line, context);
// Check if tap was on our line
if(CGRectContainsPoint(lineBounds, point))
{
NSLog(@"Tapped line");
CFArrayRef runs = CTLineGetGlyphRuns(line);
for(CFIndex j = 0; j < CFArrayGetCount(runs); j++)
{
CTRunRef run = CFArrayGetValueAtIndex(runs, j);
CFRange urlStringRange = CTRunGetStringRange(run);
CGRect runBounds = CTRunGetImageBounds(run, context, urlStringRange);
if(CGRectContainsPoint(runBounds, point))
{
NSLog(@"Tapped run");
CFIndex* buffer = malloc(sizeof(CFIndex) * urlStringRange.length);
CTRunGetStringIndices(run, urlStringRange, buffer);
// TODO: Map the glyph indices to indexes in the string & Fire the delegate
}
}
}
}
}
It's not the prettiest code at the moment, I'm still trying to just make it work, so forgive the code quality.
The problem I'm having is that when I tap outside the link, what I expect would happen, happens: Nothing gets fired.
However, I would expect "Tapped line"
to get printed if I tap the same line the link is on, which doesn't happen, and I would expect both "Tapped line"
and "Tapped run"
to get printed if I tap on the URL.
I'm unsure as to where to take this further, the resources I've looked at for resolution to this issue are Cocoa specific (which is almost completely inapplicable), or lacking information on this specific case.
I'll gladly take pointers to documentation which detail how to properly go about detecting if a touch occurred within the bounds of a core text drawing over code, but at this point, I just want to resolve this problem, so any help would be greatly appreciated.
UPDATE: I have narrowed down my problem to a coordinate issue. I have flipped the coordinates (and not as shown above) and the problem I'm getting is that touches register as I'd expect, but the coordinate space is flipped, and I can't seem to flip it back.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我刚刚这样做是为了从触摸位置获取字符串字符索引。在这种情况下,行号将为 i:
I have just done this to get the string character index from the touch position. The line number would be i in this case:
您可以尝试将文本绘图添加到 CALayer 中,将这个新图层添加为视图图层的子图层,并在您的 TouchesEnded 中对其进行命中测试?如果这样做,您应该能够通过使图层大于绘制的文本来创建更大的可触摸区域。
You could try to add your text drawing into a CALayer, add this new layer as sublayer of your views layer and hittest against it in your touchesEnded? If you do so, you should be able to create a bigger touchable area by making the layer bigger than the drawn text.
Swift 3 版本 Nick H247 的回答:
Swift 3 version of Nick H247's answer: