2012-02-03 5 views
40

Ho bisogno di trovare il pixel-frame per diversi intervalli in una vista testo. Sto usando il - (CGRect)firstRectForRange:(UITextRange *)range; per farlo. Tuttavia non riesco a scoprire come creare effettivamente un UITextRange.Crea UITextRange da NSRange

Fondamentalmente questo è quello che sto cercando:

- (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView { 

    UITextRange*range2 = [UITextRange rangeWithNSRange:range]; //DOES NOT EXIST 
    CGRect rect = [textView firstRectForRange:range2]; 
    return rect; 
} 

Apple dice uno deve sottoclasse UITextRange e UITextPosition al fine di adottare il protocollo UITextInput. Non lo faccio, ma ho comunque provato, seguendo il codice di esempio del documento e passando la sottoclasse a firstRectForRange che ha provocato l'arresto anomalo.

Se c'è un modo più semplice per aggiungere colori diversi UILables a una vista testo, per favore dimmi. Ho provato a utilizzare UIWebView con content editable impostato su TRUE, ma non mi piace comunicare con JS e colorare è l'unica cosa di cui ho bisogno.

Grazie in anticipo.

+0

Devi sottoclasse 'UITextRange' per poterlo impostare. L'unico modo per impostare le proprietà 'UITextRange' è accedervi nella sottoclasse. Definisce 'start' e' end' come proprietà, ma possono essere impostate internamente facendo riferimento a '_start' e' _end'. – Justin

+0

Sì, ma come faccio quindi a creare una UITextPosition, che non ha alcuna proprietà? 0.o Se lo sottoclassi, come potrebbe il 'firstRectForRange' sapere quale proprietà usare dalla sottoclasse UITextPosition? –

+0

Questo è qualcosa che non so. Tutto quello che so su questo è che devi sottoclasse per impostare proprietà 'readonly'. Questo è il motivo per cui questo è un commento al posto di una risposta. – Justin

risposta

80

È possibile creare un intervallo di testo con il metodo textRangeFromPosition:toPosition. Questo metodo richiede due posizioni, quindi è necessario calcolare le posizioni per l'inizio e la fine dell'intervallo. Questo viene fatto con il metodo positionFromPosition:offset, che restituisce una posizione da un'altra posizione e un offset di carattere.

- (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView 
{ 
    UITextPosition *beginning = textView.beginningOfDocument; 
    UITextPosition *start = [textView positionFromPosition:beginning offset:range.location]; 
    UITextPosition *end = [textView positionFromPosition:start offset:range.length]; 
    UITextRange *textRange = [textView textRangeFromPosition:start toPosition:end]; 
    CGRect rect = [textView firstRectForRange:textRange]; 
    return [textView convertRect:rect fromView:textView.textInputView]; 
} 
+5

Ho una piccola osservazione. Funziona solo se TextField è il primo a rispondere. Altrimenti tutte le posizioni saranno NULL e il rect ha una dimensione zero rispettivamente. Se qualcuno ha un suggerimento su come si può ottenere il rect senza che il campo di testo sia il primo a rispondere, per favore condividilo con noi. – thomas

+0

Grazie mille, Nicolas! Questo dovrebbe davvero essere documentato meglio .. –

+1

Fantastico! Mi ha aiutato molto @thomas per me funziona senza che 'UITextView' sia il primo a rispondere (ho un UITextView di sola lettura all'interno di un 'UITableViewCell'). – mluisbrown

15

È un po 'ridicolo che sembra essere così complicato. A "soluzione" semplice sarebbe quella di selezionare l'intervallo (accetta NSRange) e quindi leggere il selectedTextRange (restituisce UITextRange):

- (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView { 
    textView.selectedRange = range; 
    UITextRange *textRange = [textView selectedTextRange]; 
    CGRect rect = [textView firstRectForRange:textRange]; 
    return rect; 
} 

questo ha lavorato per me, anche se il textView non è la prima responder.


Se non si desidera che la selezione a persistere, è possibile ripristinare il selectedRange:

textView.selectedRange = NSMakeRange(0, 0); 

... o salvare la selezione corrente e ripristinarlo successivamente

NSRange oldRange = textView.selectedRange; 
// do something 
// then check if the range is still valid and 
textView.selectedRange = oldRange; 
+0

in iOS 7 del TextView deve essere selezionabile, altrimenti selectedTextRange: ritorna nil – ZeCodea

+1

Che è così ridicolmente semplice ... e anche se non è selezionabile, funziona ancora su iOS 7. – brandonscript

+0

Confermato. Che funzioni. – ervinbosenbacher

-1

Here is explain.

A UITextRan l'oggetto ge rappresenta un intervallo di caratteri in un contenitore di testo ; in altre parole, identifica un indice iniziale e un indice finale nella stringa che supporta un oggetto di immissione testo.

Le classi che adottano il protocollo UITextInput devono creare oggetti UITextRange personalizzati per gli intervalli all'interno del testo gestito dalla classe . Gli indici iniziale e finale dell'intervallo sono rappresentati da oggetti UITextPosition. Il sistema di testo utilizza entrambi gli oggetti UITextRange e UITextPosition per comunicare le informazioni relative al layout di testo .Ci sono due ragioni per l'utilizzo di oggetti per il testo gamme piuttosto che i tipi primitivi come NSRange:

Alcuni documenti contengono elementi annidati (ad esempio, i tag HTML e oggetti incorporati) e avete bisogno di tenere traccia sia posizione assoluta e posizione nel testo visibile.

Il framework WebKit, su cui è basato il sistema di testo dell'iPhone, richiede che gli indici e gli offset di testo siano rappresentati da oggetti.

Se si adotta il protocollo UITextInput, è necessario creare una sottoclasse UITextRange personalizzata e una sottoclasse UITextPosition personalizzata.

Per esempio come in those sources

5

alla domanda del titolo, ecco un Swift 2 estensione che crea un UITextRange da un NSRange.

L'unico inizializzatore di UITextRange è un metodo di istanza sul protocollo UITextInput, così l'estensione richiede inoltre si passa UITextInput come UITextField o UITextView.

extension NSRange { 
    func toTextRange(textInput textInput:UITextInput) -> UITextRange? { 
     if let rangeStart = textInput.positionFromPosition(textInput.beginningOfDocument, offset: location), 
      rangeEnd = textInput.positionFromPosition(rangeStart, offset: length) { 
      return textInput.textRangeFromPosition(rangeStart, toPosition: rangeEnd) 
     } 
     return nil 
    } 
} 
2

Swift 4 della risposta di Andrew Schreiber per una facile copia/incolla

extension NSRange { 
    func toTextRange(textInput:UITextInput) -> UITextRange? { 
     if let rangeStart = textInput.position(from: textInput.beginningOfDocument, offset: location), 
      let rangeEnd = textInput.position(from: rangeStart, offset: length) { 
      return textInput.textRange(from: rangeStart, to: rangeEnd) 
     } 
     return nil 
    } 
} 
0

Swift 4 della risposta di Nicolas Bachschmidt come UITextView interno utilizzando swifty Range<String.Index> invece di NSRange:

extension UITextView { 
    func frame(ofTextRange range: Range<String.Index>?) -> CGRect? { 
     guard let range = range else { return nil } 
     let length = range.upperBound.encodedOffset-range.lowerBound.encodedOffset 
     guard 
      let start = position(from: beginningOfDocument, offset: range.lowerBound.encodedOffset), 
      let end = position(from: start, offset: length), 
      let txtRange = textRange(from: start, to: end) 
      else { return nil } 
     let rect = self.firstRect(for: txtRange) 
     return self.convert(rect, to: textInputView) 
    } 
} 

Possibile utilizzare:

guard let rect = textView.frame(ofTextRange: text.range(of: "awesome")) else { return } 
let awesomeView = UIView() 
awesomeView.frame = rect.insetBy(dx: -3.0, dy: 0) 
awesomeView.layer.borderColor = UIColor.black.cgColor 
awesomeView.layer.borderWidth = 1.0 
awesomeView.layer.cornerRadius = 3 
self.view.insertSubview(awesomeView, belowSubview: textView)