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

时间:2010-09-27 09:53:55

标签: iphone objective-c ipad core-text

我在视图中有一些代码,它使用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""Tapped run"都可以如果点击网址就会打印出来。

我不确定在哪里进一步采取这些措施,我为解决这个问题而考虑的资源是Cocoa特定的(几乎完全不适用),或者缺乏关于这个特定情况的信息。

我很乐意接受文档的指示,详细说明如何正确地检测是否在代码的核心文本绘图范围内发生了触摸,但此时,我只是想解决这个问题,所以任何帮助非常感谢。

更新:我已将问题范围缩小到坐标问题。我已经翻转了坐标(并没有如上所示),我得到的问题是按照我的预期触及寄存器,但是坐标空间被翻转,我似乎无法将其翻转。

3 个答案:

答案 0 :(得分:9)

我刚刚完成此操作以从触摸位置获取字符串字符索引。在这种情况下,行号将是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);
}

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

斯威夫特3版尼克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
        }
    }
}