Использование CoreText и касаний для создания интерактивного действия - PullRequest
9 голосов
/ 27 сентября 2010

У меня есть некоторый код в представлении, который рисует некоторый приписанный текст, используя CoreText.В этом я ищу URL и делаю их синими.Идея состоит в том, чтобы не вносить все издержки UIWebView только для того, чтобы получать кликабельные ссылки.Когда пользователь нажимает на эту ссылку (а не на всю ячейку табличного представления), я хочу запустить метод делегата, который затем будет использован для представления модального представления, содержащего веб-представление, переходящее по этому URL.

Я сохраняю путь и саму строку как переменные экземпляра представления, и код рисования происходит в -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" будут напечатаны, если я нажму на URL.

Я не уверен, где взять это дальше, ресурсы, которые я 'Мы рассмотрели для решения этой проблемы специфические для Какао (которые почти полностью неприменимы), или не хватает информации по этому конкретному случаю.

Я с удовольствием возьму указатели на документацию, которая подробно описывает, как правильно обнаружить, еслиприкосновение произошло в рамках основного текста, рисующего код, но на данный момент я просто хочу решить эту проблему, поэтому любая помощь будет принята с благодарностью.

ОБНОВЛЕНИЕ : IчасЯ сузил мою проблему до проблемы координат.Я перевернул координаты (а не как показано выше), и проблема, которую я получаю, состоит в том, что касания регистрируются, как я ожидал, но пространство координат перевернуто, и я не могу перевернуть его обратно.

Ответы [ 3 ]

9 голосов
/ 12 апреля 2012

Я только что сделал это, чтобы получить индекс строки символов из позиции касания. В этом случае номер строки будет 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);
}
0 голосов
/ 06 января 2017

Swift 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
        }
    }
}
0 голосов
/ 27 сентября 2010

Вы можете попытаться добавить свой текстовый рисунок в CALayer, добавить этот новый слой в качестве подслоя вашего слоя представлений и нанести удар по нему в ваших прикосновенияхEnded? Если вы это сделаете, вы сможете создать большую сенсорную область, сделав слой больше, чем нарисованный текст.

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