2010-04-13 1 views
21

C'è un modo per ottenere la posizione (CGPoint) del cursore (barra lampeggiante) in un UITextView (preferibile rispetto al suo contenuto). Non intendo il luogo come un NSRange. Ho bisogno di qualcosa in giro:Posizione pixel del cursore in UITextView

- (CGPoint)cursorPosition; 

Dovrebbe essere un modo API non privato.

+0

Stai parlando del punto di inserimento del testo? io.e la barra verticale lampeggiante? –

+0

Sì, questo è ciò che intendo. –

+0

Non esiste un metodo pubblico per ottenere tali informazioni. Cosa stai cercando di ottenere? Potrebbe esserci un altro modo. –

risposta

10

È doloroso, ma è possibile utilizzare le aggiunte UIStringDrawing a NSString per farlo. Ecco l'algoritmo generale che ho usato:

CGPoint origin = textView.frame.origin; 
NSString* head = [textView.text substringToIndex:textView.selectedRange.location]; 
CGSize initialSize = [head sizeWithFont:textView.font constrainedToSize:textView.contentSize]; 
NSUInteger startOfLine = [head length]; 
while (startOfLine > 0) { 
    /* 
    * 1. Adjust startOfLine to the beginning of the first word before startOfLine 
    * 2. Check if drawing the substring of head up to startOfLine causes a reduction in height compared to initialSize. 
    * 3. If so, then you've identified the start of the line containing the cursor, otherwise keep going. 
    */ 
} 
NSString* tail = [head substringFromIndex:startOfLine]; 
CGSize lineSize = [tail sizeWithFont:textView.font forWidth:textView.contentSize.width lineBreakMode:UILineBreakModeWordWrap]; 
CGPoint cursor = origin; 
cursor.x += lineSize.width; 
cursor.y += initialSize.height - lineSize.height; 
return cursor; 
} 

ho usato [NSCharacterSet whitespaceAndNewlineCharacterSet] trovare confini di parola.

Questo può essere fatto anche (presumibilmente in modo più efficiente) utilizzando CTFrameSetter in CoreText, ma che non è disponibile in iPhone OS 3.1.3, quindi se ci si rivolge l'iPhone è necessario attenersi a UIStringDrawing.

+1

È una soluzione piacevole con cui ho giocato anche io. Per trovare i limiti delle parole, ho trovato preferibile utilizzare CFStringTokenizer. L'implementazione non fornisce le coordinate x e y, ma piuttosto la dimensione dei caratteri nella riga che contiene il punto di inserimento fino al punto di inserimento, giusto? Tuttavia, accetto il tuo approccio come una risposta poiché alla fine ho fatto qualcosa di simile adottato per le mie esigenze. –

+0

Grazie per il puntatore su CFStringTokenizer, ho trascurato questo. Sottraendo lineSize.height da initialSize.height otterrai lo spostamento y del cursore (relativo a UITextView) e la larghezza del frammento di linea finale ti darà l'offset x. – Tony

+0

È possibile elaborare ulteriori informazioni su /* * 1. Regolare startOfLine all'inizio della prima parola prima di startOfLine * 2. Verificare se il disegno della sottostringa di testa su startOfLine provoca una riduzione dell'altezza rispetto a initialSize. * 3. Se è così, allora hai identificato l'inizio della linea contenente il cursore, altrimenti continua. */ Richiesto anche la posizione del cursore in UITextView. – tek3

3

Sì - come in c'è un metodo per ottenere la posizione del cursore. Basta usare

CGRect caretRect = [textView rectContainingCaretSelection]; 
return caretRect.origin; 

No - come in questo metodo è privato. Non ci sono API pubbliche per questo.

+0

Esattamente questo metodo sarebbe quello che cerco, ma ovviamente non posso usare l'API privata. Altra soluzione? –

+2

Non sembra esistere in iOS 4.2. Non ottengo alcuna eccezione di selezione del genere dal mio UITextView. – mxcl

1

Provo a contrassegnare un testo selezionato, ad esempio, ricevo un NSRange e voglio disegnare un rettangolo giallo dietro quel testo. C'è un altro modo?

posso consigliarvi qualche trucco:

NSRange selectedRange = myTextView.selectedRange; 
[myTextView select:self]; 
UIMenuController* sharedMenu = [UIMenuController sharedMenuController]; 
CGRect menuFrame = [sharedMenu menuFrame]; 
[sharedMenu setMenuVisible:NO]; 
myTextView.selectedRange = selectedRange 

Usando questo codice, è possibile sapere ottenere la posizione della copia menù passato taglio// e non ci posizionare il rettangolo giallo.

Non ho trovato un modo per ottenere la posizione del menu senza forzarlo a comparire con un'operazione di selezione simulata.

saluti Assayag

+0

L'ho implementato per il test ed è un bel trucco, ma in realtà non è utile in quanto non è possibile concludere da menuFrame alla posizione del cursore (problemi quando la freccia punta verso il basso e non verso l'alto ecc.). –

1

prendere uno screenshot del UITextView, quindi cercare i dati dei pixel per i colori che corrispondono al colore del cursore.

-(CGPoint)positionOfCursorForTextView:(UITextView)textView { 
    //get CGImage from textView 
    UIGraphicsBeginImageContext(textView.bounds.size); 
    [textView.layer renderInContext:UIGraphicsGetCurrentContext()]; 
    CGImageRef textImageRef = UIGraphicsGetImageFromCurrentImageContext().CGImage; 
    UIGraphicsEndImageContext(); 

    //get raw pixel data 
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 
    uint8_t * textBuffer = (uint8_t*)malloc(Width * Height * 4); 
    NSUInteger bytesPerRow = 4 * Width; 
    NSUInteger bitsPerComponent = 8; 
    CGContextRef context = CGBitmapContextCreate(textBuffer, Width, Height, 
              bitsPerComponent, bytesPerRow, colorSpace, 
              kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); 


    CGColorSpaceRelease(colorSpace); 

    CGContextDrawImage(context, CGRectMake(0, 0, Width, Height), textImageRef); 
    CGContextRelease(context); 

    //search 

    for(int y = 0; y < Height; y++) 
    { 
     for(int x = 0; x < Width * 4; x += 4) 
     { 
      int red = textBuffer[y * 4 * (NSInteger)Width + x]; 
      int green = textBuffer[y * 4 * (NSInteger)Width + x + 1];  
      int blue = textBuffer[y * 4 * (NSInteger)Width + x + 2]; 
      int alpha = textBuffer[y * 4 * (NSInteger)Width + x + 3];  


      if(COLOR IS CLOSE TO COLOR OF CURSOR) 
      { 
       free(textBuffer); 
       CGImageRelease(textImageRef); 
       return CGPointMake(x/4, y); 
      } 
     } 
    } 

    free(textBuffer); 
    CGImageRelease(textImageRef); 
    return CGPointZero; 
} 
+7

Yay per risposta creativa, nay per errore incline e calcolo intensivo. –

+0

puoi dirmi che cos'è Larghezza e Altezza sul tuo codice ??? –

+1

wow !!!!! la sua risposta davvero diversa è la ragione per cui ho votato. – Tirth

46

richiede iOS 5

CGPoint cursorPosition = [textview caretRectForPosition:textview.selectedTextRange.start].origin; 

ricordatevi di controllare che non selectedTextRangenil è prima di chiamare questo metodo. Dovresti anche usare selectedTextRange.empty per verificare che sia la posizione del cursore e non l'inizio di un intervallo di testo. Quindi:

if (textview.selectedTextRange.empty) { 
    // get cursor position and do stuff ... 
} 
+3

Questo è solo iOS 5. Sebbene -caretRectForPosition sia disponibile 3.2 e successive, UITextView non è conforme a UITextInput fino a iOS 5 –

+0

Grazie Jonathan, ho aggiornato la risposta. – Craz

1

SWIFT 4 versione:

if let cursorPosition = textView.selectedTextRange?.start { 
    // cursorPosition is a UITextPosition object describing position in the text (text-wise description) 

    let caretPositionRectangle: CGRect = textView.caretRect(for: cursorPosition) 
    // now use either the whole rectangle, or its origin (caretPositionRectangle.origin) 
} 

textView.selectedTextRange?.start restituisce una posizione del testo del cursore, e quindi è sufficiente utilizzare textView.caretRect(for:) per ottenere la sua posizione di pixel in textView.