J'ai besoin de trouver le pixel-frame pour différentes plages dans un textview. J'utilise le - (CGRect)firstRectForRange:(UITextRange *)range;
pour le faire. Cependant, je ne trouve pas comment créer une UITextRange
.
Fondamentalement, voici ce que je recherche:
- (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView {
UITextRange*range2 = [UITextRange rangeWithNSRange:range]; //DOES NOT EXIST
CGRect rect = [textView firstRectForRange:range2];
return rect;
}
Apple indique qu'il faut sous-classer UITextRange
et UITextPosition
pour adopter le protocole UITextInput
. Je ne le fais pas, mais j'ai quand même essayé, en suivant l'exemple de code du doc et en passant la sous-classe à firstRectForRange
, ce qui a entraîné un crash.
S'il existe un moyen plus facile d'ajouter une UILables
de couleur différente à une vue en texte, dites-le-moi. J'ai essayé d'utiliser UIWebView avec content editable
défini sur TRUE, mais la communication avec JS ne me passionne pas et la coloration est la seule chose dont j'ai besoin.
Merci d'avance.
Vous pouvez créer une plage de texte avec la méthode textRangeFromPosition:toPosition
. Cette méthode nécessite deux positions. Vous devez donc calculer les positions pour le début et la fin de votre plage. Cela se fait avec la méthode positionFromPosition:offset
, qui retourne une position à partir d'une autre position et un offset de caractères.
- (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];
}
C'est un peu ridicule qui semble être si compliqué. Une solution de contournement simple consiste à sélectionner la plage (accepte NSRange), puis à lire le selectedTextRange (renvoie UITextRange):
- (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView {
textView.selectedRange = range;
UITextRange *textRange = [textView selectedTextRange];
CGRect rect = [textView firstRectForRange:textRange];
return rect;
}
Cela a fonctionné pour moi même si textView n'est pas le premier répondant.
Si vous ne voulez pas que la sélection persiste, vous pouvez réinitialiser selectedRange:
textView.selectedRange = NSMakeRange(0, 0);
... ou enregistrez la sélection actuelle et restaurez-la ensuite
NSRange oldRange = textView.selectedRange;
// do something
// then check if the range is still valid and
textView.selectedRange = oldRange;
Pour la question du titre, voici une extension Swift 2 qui crée un UITextRange à partir d’un NSRange.
Le seul initialiseur pour UITextRange est une méthode d'instance sur le protocole UITextInput; l'extension nécessite donc que vous passiez également à UITextInput, tel que UITextField ou 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
}
}
Swift 4 de la réponse d'Andrew Schreiber pour un copier/coller facile
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
}
}
Swift 4 de la réponse de Nicolas Bachschmidt sous la forme d'une extension UITextView
utilisant swifty Range<String.Index>
au lieu de 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)
}
}
Utilisation possible:
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)