wake-up-neo.com

Erstellen Sie UITextRange aus NSRange

Ich muss den Pixelrahmen für verschiedene Bereiche in einer Textansicht finden. Ich benutze die - (CGRect)firstRectForRange:(UITextRange *)range;, um es zu tun. Ich kann jedoch nicht herausfinden, wie man tatsächlich eine UITextRange erstellt.

Im Grunde ist es das, was ich suche:

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

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

Apple sagt, man muss UITextRange und UITextPosition in die Unterklasse unterteilen, um das UITextInput-Protokoll zu übernehmen. Ich mache das nicht, aber ich habe es trotzdem versucht, dem Beispielcode des Dokuments zu folgen und die Unterklasse an firstRectForRange zu übergeben, was zum Absturz führte.

Wenn es eine einfachere Möglichkeit gibt, einer Textansicht andersfarbige UILables hinzuzufügen, teilen Sie mir dies bitte mit. Ich habe versucht, UIWebView mit content editable auf TRUE zu setzen, aber ich mag es nicht, mit JS zu kommunizieren, und das Malen ist das einzige, was ich brauche.

Danke im Voraus.

46
Johannes Lund

Sie können einen Textbereich mit der Methode textRangeFromPosition:toPosition erstellen. Diese Methode erfordert zwei Positionen. Daher müssen Sie die Positionen für den Anfang und das Ende Ihres Bereichs berechnen. Dies geschieht mit der Methode positionFromPosition:offset, die eine Position von einer anderen Position und einen Zeichenversatz zurückgibt.

- (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];
}
89

Es ist ein bisschen lächerlich, das scheint so kompliziert zu sein. Eine einfache "Problemumgehung" wäre, den Bereich auszuwählen (akzeptiert NSRange) und dann den ausgewählten TextRange zu lesen (gibt UITextRange zurück)

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

Dies funktionierte für mich auch, wenn das textView nicht der erste Ansprechpartner ist. 


Wenn Sie nicht möchten, dass die Auswahl bestehen bleibt, können Sie entweder den ausgewählten Bereich zurücksetzen:

textView.selectedRange = NSMakeRange(0, 0);

... oder speichern Sie die aktuelle Auswahl und stellen Sie sie anschließend wieder her

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

Zur Titelfrage gibt es hier eine Swift-2-Erweiterung, die einen UITextRange aus einem NSRange erstellt.

Der einzige Initialisierer für UITextRange ist eine Instanzmethode für das UITextInput-Protokoll. Daher müssen Sie für die Erweiterung auch UITextInput wie UITextField oder UITextView übergeben.

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

Swift 4 von Andrew Schreibers Antwort für einfaches Kopieren/Einfügen

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
    }
}
5
CodenameDuchess

Swift 4 von Nicolas Bachschmidts Antwort als UITextView-Erweiterung mit swifty Range<String.Index> anstelle von 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)
    }
}

Mögliche Verwendung:

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)
0
Victor