2012-02-03 22 views
40

Muszę znaleźć ramkę pikseli dla różnych zakresów w widoku tekstowym. Używam do tego celu - (CGRect)firstRectForRange:(UITextRange *)range;. Jednak nie mogę się dowiedzieć, jak faktycznie utworzyć UITextRange.Utwórz UITextRange z NSRange

Zasadniczo jest to co szukam:

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

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

Apple mówi jeden ma podklasy UITextRange i UITextPosition w celu przyjęcia protokołu UITextInput. Nie robię tego, ale mimo to próbowałem, postępując zgodnie z przykładowym kodem doktora i przekazując podklasę do firstRectForRange, co spowodowało awarię.

Jeśli jest łatwiejszy sposób dodawania różnych kolorowych UILables do tekstu, proszę powiedz mi. Próbowałem używać UIWebView z content editable ustawionym na PRAWDA, ale nie lubię komunikować się z JS, a kolorowanie jest jedyną rzeczą, której potrzebuję.

Z góry dziękuję.

+0

Musisz mieć podklasę "UITextRange", aby móc ją ustawić. Jedynym sposobem na ustawienie właściwości "UITextRange" jest uzyskanie dostępu do ich podklasy. Definiuje on 'start' i' end' jako właściwości, ale można je ustawić wewnętrznie, odwołując się do '_start' i' _end'. – Justin

+0

Tak, ale jak utworzyć UITextPosition, która nie ma żadnych właściwości? Jeśli tak samo podklasuję, to w jaki sposób "firstRectForRange" może wiedzieć, której właściwości użyć z mojej podklasy UITextPosition? –

+0

To jest coś, czego nie wiem. Wiem tylko, że musisz podklasować, aby ustawić właściwości 'readonly'. Dlatego jest to komentarz zamiast odpowiedzi. – Justin

Odpowiedz

80

Możesz utworzyć zakres tekstowy za pomocą metody textRangeFromPosition:toPosition. Ta metoda wymaga dwóch pozycji, więc musisz obliczyć pozycje początkowe i końcowe zakresu. Odbywa się to za pomocą metody positionFromPosition:offset, która zwraca pozycję z innej pozycji i przesunięcie znaku.

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

Mam małą uwagę. Działa to tylko wtedy, gdy TextField jest pierwszą odpowiedzią. W przeciwnym razie wszystkie pozycje będą miały wartość NULL, a rect ma odpowiednio zerowy rozmiar. Jeśli ktoś ma podpowiedź, jak można uzyskać rect bez TextField jako pierwszej odpowiedzi, proszę podziel się nim z nami. – thomas

+0

Wielkie dzięki, Nicolas! To powinno być naprawdę udokumentowane lepiej .. –

+1

Awesome! Bardzo mi pomogło. @Thomas dla mnie działa bez 'UITextView' będącego pierwszym responder (mam tylko do odczytu' UITextView' wewnątrz 'UITableViewCell'). – mluisbrown

15

To trochę niedorzeczne, wydaje się być tak skomplikowane. Prosty „obejście” byłoby, aby wybrać zakres (akceptuje NSRange), a następnie odczytać selectedTextRange (zwraca UITextRange):

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

ten pracował dla mnie, nawet jeśli nie jest TextView najpierw reagował.


Jeśli nie chcesz, aby wybór utrzymują się, można zresetować selectedRange:

textView.selectedRange = NSMakeRange(0, 0); 

... lub zapisać bieżący wybór i przywrócić go później

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

w iOS 7 musi być do wyboru tekst, w przeciwnym razie selectedTextRange: zwraca zero – ZeCodea

+1

To jest tak absurdalnie proste ... i nawet jeśli nie można go wybrać, to nadal działa na iOS 7. – brandonscript

+0

Potwierdzone. To działa. – ervinbosenbacher

-1

Here is explain.

A UITextRan obiekt ge reprezentuje zakres znaków w kontenerze tekstowym ; innymi słowy, identyfikuje indeks początkowy i indeks końcowy w łańcuchu wspierającym obiekt wprowadzania tekstu.

Klasy, które przyjmują protokół UITextInput, muszą utworzyć niestandardowe obiektyUITextRange, aby reprezentować zakresy w tekście zarządzanym przez klasę . Początkowe i końcowe indeksy zakresu to reprezentowane przez obiekty UITextPosition. System tekstowy wykorzystuje zarówno obiekty UITextRange, jak i UITextPosition do komunikowania informacji w układzie tekstowym .Istnieją dwa powody korzystania obiektów tekstu zakresy raczej niż prymitywne typy takie jak NSRange:

Niektóre dokumenty zawierają elementy zagnieżdżone (na przykład znaczniki HTML i osadzone obiekty) i trzeba śledzić zarówno absolutną pozycję i pozycję w widocznym tekście.

Struktura WebKit, na której oparty jest system tekstowy iPhone, wymaga, aby indeksy tekstowe i korekcje były reprezentowane przez obiekty.

Po przyjęciu protokołu UITextInput należy utworzyć niestandardową podklasę UITextRange , a także niestandardową podklasę UITextPosition.

Na przykład jak w those sources

5

do tytułu pytanie o to Swift 2 rozszerzenie że tworzy UITextRange z NSRange.

Jedynym initializer dla UITextRange jest metoda instancji na protokole UITextInput, a tym samym rozszerzenie wymaga również zdać w UITextInput takich jak UITextField lub 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 odpowiedzi Andrew Schreibera do łatwego kopiowania/wklejania

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 odpowiedzi Nicolasa Bachschmidt jako przedłużenie UITextView wykorzystaniem Swifty Range<String.Index> zamiast 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) 
    } 
} 

Possible użyj:

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)