使用 CoreText 和触摸创建可点击的操作

发布于 2024-09-24 21:48:50 字数 2509 浏览 3 评论 0原文

我在视图中有一些代码,它使用 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

始终不够爱げ你 2024-10-01 21:48:50

我刚刚这样做是为了从触摸位置获取字符串字符索引。在这种情况下,行号将为 i:

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"touch ended");

    UITouch* touch = [touches anyObject];

    CGPoint pnt = [touch locationInView:self];

    CGPoint reversePoint = CGPointMake(pnt.x, self.frame.size.height-pnt.y);

    CFArrayRef lines = CTFrameGetLines(ctFrame);

    CGPoint* lineOrigins = malloc(sizeof(CGPoint)*CFArrayGetCount(lines));

    CTFrameGetLineOrigins(ctFrame, CFRangeMake(0,0), lineOrigins);

    for(CFIndex i = 0; i < CFArrayGetCount(lines); i++)
    {
        CTLineRef line = CFArrayGetValueAtIndex(lines, i);

        CGPoint origin = lineOrigins[i];
        if (reversePoint.y > origin.y) {
            NSInteger index = CTLineGetStringIndexForPosition(line, reversePoint);
            NSLog(@"index %d", index);
            break;
        }
    }
    free(lineOrigins);
}

I have just done this to get the string character index from the touch position. The line number would be i in this case:

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"touch ended");

    UITouch* touch = [touches anyObject];

    CGPoint pnt = [touch locationInView:self];

    CGPoint reversePoint = CGPointMake(pnt.x, self.frame.size.height-pnt.y);

    CFArrayRef lines = CTFrameGetLines(ctFrame);

    CGPoint* lineOrigins = malloc(sizeof(CGPoint)*CFArrayGetCount(lines));

    CTFrameGetLineOrigins(ctFrame, CFRangeMake(0,0), lineOrigins);

    for(CFIndex i = 0; i < CFArrayGetCount(lines); i++)
    {
        CTLineRef line = CFArrayGetValueAtIndex(lines, i);

        CGPoint origin = lineOrigins[i];
        if (reversePoint.y > origin.y) {
            NSInteger index = CTLineGetStringIndexForPosition(line, reversePoint);
            NSLog(@"index %d", index);
            break;
        }
    }
    free(lineOrigins);
}
忆梦 2024-10-01 21:48:50

您可以尝试将文本绘图添加到 CALayer 中,将这个新图层添加为视图图层的子图层,并在您的 TouchesEnded 中对其进行命中测试?如果这样做,您应该能够通过使图层大于绘制的文本来创建更大的可触摸区域。

// hittesting 
UITouch *touch = [[event allTouches] anyObject];
touchedLayer = (CALayer *)[self.layer hitTest:[touch locationInView:self]];

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.

// hittesting 
UITouch *touch = [[event allTouches] anyObject];
touchedLayer = (CALayer *)[self.layer hitTest:[touch locationInView:self]];
月朦胧 2024-10-01 21:48:50

Swift 3 版本 Nick H247 的回答:

var ctFrame: CTFrame?

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let touch = touches.first, let ctFrame = self.ctFrame else { return }

    let pnt = touch.location(in: self.view)
    let reversePoint = CGPoint(x: pnt.x, y: self.frame.height - pnt.y)
    let lines = CTFrameGetLines(ctFrame) as [AnyObject] as! [CTLine]

    var lineOrigins = [CGPoint](repeating: .zero, count: lines.count)
    CTFrameGetLineOrigins(ctFrame, CFRange(location: 0, length: 0), &lineOrigins)

    for (i, line) in lines.enumerated() {
        let origin = lineOrigins[i]

        if reversePoint.y > origin.y {
            let index = CTLineGetStringIndexForPosition(line, reversePoint)
            print("index \(index)")
            break
        }
    }
}

Swift 3 version of Nick H247's answer:

var ctFrame: CTFrame?

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let touch = touches.first, let ctFrame = self.ctFrame else { return }

    let pnt = touch.location(in: self.view)
    let reversePoint = CGPoint(x: pnt.x, y: self.frame.height - pnt.y)
    let lines = CTFrameGetLines(ctFrame) as [AnyObject] as! [CTLine]

    var lineOrigins = [CGPoint](repeating: .zero, count: lines.count)
    CTFrameGetLineOrigins(ctFrame, CFRange(location: 0, length: 0), &lineOrigins)

    for (i, line) in lines.enumerated() {
        let origin = lineOrigins[i]

        if reversePoint.y > origin.y {
            let index = CTLineGetStringIndexForPosition(line, reversePoint)
            print("index \(index)")
            break
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文