2010-09-27 8 views
9

ho qualche codice in una vista che richiama un testo attribuito utilizzando CoreText. In questo, sto cercando gli URL e li faccio diventare blu. L'idea è di non portare tutto il sovraccarico di un UIWebView solo per ottenere link cliccabili. Una volta che un utente tocca quel collegamento (non l'intera cella di visualizzazione tabella), voglio attivare un metodo delegato che verrà quindi utilizzato per presentare una vista modale che contiene una vista Web che va a quell'URL.Utilizzando CoreText e tocca per creare un'azione cliccabile

Sto salvando il percorso e la stringa stessa come variabili di istanza della vista e il codice di disegno si verifica in -drawRect: (l'ho lasciato per brevità).

mio gestore tocco tuttavia, pur incompleta, non è stampa quello che mi aspettavo che. Si tratta qui di seguito:

- (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 
       } 
      } 
     } 
    } 
} 

non è il codice più bella in questo momento, sto ancora cercando di fare solo il lavoro, in modo da perdonare la qualità del codice.

Il problema che sto avendo è che quando si tocca il link di fuori, quello che mi aspetto sarebbe accaduto, accade: Nulla viene licenziato.

Tuttavia, mi aspetterei "Tapped line" per ottenere stampati se si tocca la stessa linea il collegamento è attivo, cosa che non accade, e mi aspetto sia "Tapped line" e "Tapped run" per ottenere stampati se si tocca sull'URL.

Non sono sicuro di dove andare oltre, le risorse che ho visto per la risoluzione di questo problema sono specifiche del cacao (che è quasi completamente inapplicabile) o mancano di informazioni su questo caso specifico.

Prenderò volentieri un suggerimento alla documentazione che descrive dettagliatamente come rilevare correttamente se un tocco si è verificato entro i limiti di un testo di base che si disegna sul codice, ma a questo punto, voglio solo risolvere questo problema, quindi qualsiasi l'aiuto sarebbe molto apprezzato.

UPDATE: Ho ristretto il mio problema a un problema di coordinate. Ho capovolto le coordinate (e non come indicato sopra) e il problema che sto ricevendo è che tocchi registrarsi come mi aspetto, ma lo spazio di coordinate è capovolto, e io non riesco a capovolgere indietro.

+0

Avete preso in considerazione il fatto che CoreText utilizza un sistema di coordinate capovolto? In questo momento mi sembra che tu stia confrontando le cose in due diversi sistemi di coordinate. – Jacques

+0

Inoltre, è una cattiva idea ricreare il framesetter su ogni tocco del genere. La creazione di un frame-set è molto costosa, quindi è consigliabile memorizzarla nella cache quando si inizia a disegnare o impostare il testo. – Jacques

+0

La mia metodologia di sviluppo è semplicemente: 1) Fallo funzionare; 2) fallo bene; 3) Renderlo veloce/pulito. Affrontiamo il n. 2 quando otteniamo # 1 in corso :) Anche per quanto riguarda il sistema di coordinate, sì, me ne rendo conto e per un po 'non lo stavo gestendo.Nel codice ora, dopo essermi consultato con un collega, mi ha impostato direttamente su questo, e ora sta rilevando almeno i tocchi sulle linee, ma non le esecuzioni. Sto ancora cercando di capirlo. – jer

risposta

8

ho appena fatto questo per ottenere l'indice di carattere stringa dalla posizione di tocco. Il numero di riga sarebbe ho in questo caso:

versione
- (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

Grazie Nick mi hai salvato! :) – stefanosn

+0

Funziona benissimo, posso confrontarlo con un insieme di intervalli che ho memorizzato per le parole intercettabili. Questo è molto più veloce del mio vecchio approccio - grazie :) –

+0

Come posso ottenere ctFrame? –

0

Si potrebbe provare ad aggiungere disegnare il testo in un CALayer, aggiungere questo nuovo livello come sottolivello della vostra vista layer e hitTest contro di essa nella vostra touchesEnded? Se lo fai, dovresti essere in grado di creare un'area più grande toccabile rendendo il livello più grande del testo disegnato.

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

Quindi avrei un altro problema di dover dividere il mio testo in due livelli diversi: quello che voglio attivare il delegato e poi il resto. Il problema in cui mi imbatterei è ciò che accade se l'URL si trova nel mezzo di una riga e c'è del testo prima e dopo di esso? Quindi dovrei dividermi in molti livelli diversi. Potrebbe diventare peloso se ci sono alcuni URL nel gruppo di testo che renderizzo. Preferirei semplicemente recuperare un elenco di tutti gli elementi nella stringa di attributo che ho colorato in blu, e ottenere i loro riquadri di delimitazione, vedere se l'utente ha toccato l'interno e chiamare il delegato con quell'URL. – jer

+0

È vero, questo potrebbe essere un problema. Forse non ho capito bene il tuo problema. Che ne dici di mantenere il testo così com'è e sovrapporre un livello hittable per catturare questi tocchi? – msg

+0

Ho ancora bisogno di sapere esattamente dove collocarlo - il che significa che ho ancora bisogno di ottenere il rect dell'URL. Questo mi riporta al punto di partenza. :) – jer

0

Swift 3 della risposta di 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 
     } 
    } 
} 
Problemi correlati