Usando CoreText e toca para criar um botão clicável de ação
-
25-09-2019 - |
Pergunta
Tenho um código em um modo de exibição que atrai alguns atribuíram o texto usando CoreText.Neste, eu estou procurando urls e tornando-os de azul.A ideia é não trazer a sobrecarga de um UIWebView
apenas para obter links clicáveis.Uma vez que um usuário toca no link (não toda a tabela, ver célula), eu gostaria de fogo fora de um delegado método que será utilizado para apresentar um modal modo de exibição que contém um modo de exibição da web para o url.
Eu estou guardando o caminho e a própria cadeia de caracteres como variáveis de instância da vista, e o código de desenho acontece em -drawRect:
(Deixei de fora por questões de brevidade).
Meu toque, processador no entanto, embora incompleto, não é a impressão que eu esperava.Ele está abaixo:
- (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
}
}
}
}
}
Não é a mais bonita de código no momento, eu ainda estou tentando apenas fazer o trabalho, então perdoe a qualidade do código.
O problema que eu estou tendo é que quando eu tocar fora o link, que eu esperava que iria acontecer, acontece:Nada é acionado.
No entanto, eu seria de esperar "Tapped line"
para obter impresso, se eu tocar a mesma linha o link está no, o que não acontece, e eu seria de esperar tanto "Tapped line"
e "Tapped run"
para obter impresso, se eu tocar no URL.
Eu não tenho certeza de onde a tomar este mais, os recursos que eu olhei para a resolução deste problema são o Cacau específico (que é quase completamente obsoleta), ou falta de informação sobre esse caso específico.
Eu vou de bom grado sugestões para outras documentações que detalhes como a forma correta de ir sobre como detectar se um toque ocorreu dentro dos limites de um núcleo de desenho de texto sobre o código, mas neste momento, eu só quero resolver esse problema, por isso qualquer ajuda seria muito apreciada.
ATUALIZAÇÃO:Eu restringiu o meu problema para uma coordenada problema.Eu ter virado as coordenadas (e não, como mostrado acima) e o problema que eu estou ficando é que toca registrar como eu esperava, mas o espaço de coordenadas é invertida, e eu não consigo lançá-lo de volta.
Solução
Eu tenho só fez isso para obter a seqüência de caracteres de índice a partir da posição de toque.O número de linha seria eu nesse caso:
- (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);
}
Outras dicas
Você pode tentar adicionar o texto de desenho em um CALayer, adicione esta nova camada subcamada de seus pontos de vista e a camada de hittest contra ele em seu touchesEnded?Se você fizer isso, você deve ser capaz de criar um maior palpável área, tornando a camada maior que a de desenho de texto.
// hittesting
UITouch *touch = [[event allTouches] anyObject];
touchedLayer = (CALayer *)[self.layer hitTest:[touch locationInView:self]];
Swift 3 versão de Nick H247 a resposta:
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
}
}
}