可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I need to find the pixel-frame for different ranges in a textview. I'm using the - (CGRect)firstRectForRange:(UITextRange *)range;
to do it. However I can't find out how to actually create a UITextRange
.
Basically this is what I'm looking for:
- (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView {
UITextRange*range2 = [UITextRange rangeWithNSRange:range]; //DOES NOT EXIST
CGRect rect = [textView firstRectForRange:range2];
return rect;
}
Apple says one has to subclass UITextRange
and UITextPosition
in order to adopt the UITextInput
protocol. I don't do that, but I tried anyway, following the doc's example code and passing the subclass to firstRectForRange
which resulted in crashing.
If there is a easier way of adding different colored UILables
to a textview, please tell me. I have tried using UIWebView with content editable
set to TRUE, but I'm not fond of communicating with JS, and coloring is the only thing I need.
Thanks in advance.
回答1:
You can create a text range with the method textRangeFromPosition:toPosition
. This method requires two positions, so you need to compute the positions for the start and the end of your range. That is done with the method positionFromPosition:offset
, which returns a position from another position and a character offset.
- (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];
}
回答2:
It is a bit ridiculous that seems to be so complicated.
A simple "workaround" would be to select the range (accepts NSRange) and then read the selectedTextRange (returns UITextRange):
- (CGRect)frameOfTextRange:(NSRange)range inTextView:(UITextView *)textView {
textView.selectedRange = range;
UITextRange *textRange = [textView selectedTextRange];
CGRect rect = [textView firstRectForRange:textRange];
return rect;
}
This worked for me even if the textView is not first responder.
If you don't want the selection to persist, you can either reset the selectedRange:
textView.selectedRange = NSMakeRange(0, 0);
...or save the current selection and restore it afterwards
NSRange oldRange = textView.selectedRange;
// do something
// then check if the range is still valid and
textView.selectedRange = oldRange;
回答3:
To the title question, here is a Swift 2 extension that creates a UITextRange from an NSRange.
The only initializer for UITextRange is a instance method on the UITextInput protocol, thus the extension also requires you pass in UITextInput such as UITextField or 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
}
}
回答4:
Swift 4 of Andrew Schreiber's answer for easy copy/paste
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:
Swift 4 of Nicolas Bachschmidt's answer as an UITextView
extension using swifty Range<String.Index>
instead of 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 use:
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)
回答6:
Here is explain.
A UITextRange object represents a range of characters in a text
container; in other words, it identifies a starting index and an
ending index in string backing a text-entry object.
Classes that adopt the UITextInput protocol must create custom
UITextRange objects for representing ranges within the text managed by
the class. The starting and ending indexes of the range are
represented by UITextPosition objects. The text system uses both
UITextRange and UITextPosition objects for communicating text-layout
information. There are two reasons for using objects for text ranges
rather than primitive types such as NSRange:
Some documents contain nested elements (for example, HTML tags and
embedded objects) and you need to track both absolute position and
position in the visible text.
The WebKit framework, which the iPhone text system is based on,
requires that text indexes and offsets be represented by objects.
If you adopt the UITextInput protocol, you must create a custom
UITextRange subclass as well as a custom UITextPosition subclass.
For example like in those sources