UITextView firstRectForRange返回错误的框架
编辑
简单的解决scheme是将任何帧计算从viewDidLoad
移动到viewDidAppear:
我很难得到以下代码正常工作
代码返回UITextView中给定的NSRange的第一帧。
它可以工作,如果没有换行符,但是当我在UITextView中添加换行符时,会出现一些奇怪的行为。
@implementation UITextView (TextFrame) - (CGRect)frameOfTextRange:(NSRange)range { UITextPosition *beginning = self.beginningOfDocument; UITextPosition *start = [self positionFromPosition:beginning offset:range.location]; UITextPosition *end = [self positionFromPosition:start offset:range.length]; UITextRange *textRange = [self textRangeFromPosition:start toPosition:end]; CGRect rect = [self firstRectForRange:textRange]; return [self convertRect:rect fromView:self.textInputView]; } @end @implementation DetailViewController - (void)viewDidLoad { [super viewDidLoad]; if (self.searchString) { CGRect rect = [self.textView frameOfTextRange:[self.textView.text rangeOfString:self.searchString]]; ... } }
显然,似乎firstRectForRange:
viewDidLoad
工作
将计算从viewDidLoad
移到viewDidAppear:
解决了这个问题
旧代码
- (void)viewDidLoad { [super viewDidLoad]; if (self.searchString) { CGRect rect = [self.textView frameOfTextRange:[self.textView.text rangeOfString:self.searchString]]; [self.textView scrollRangeToVisible:[self.textView.text rangeOfString:self.searchString]]; UIView *markerView = [[UIView alloc] initWithFrame:rect]; markerView.backgroundColor = [UIColor colorWithRed:0.810 green:0.735 blue:0.000 alpha:0.660]; [self.textView addSubview:markerView]; _searchString = nil; } }
新的和可行的代码
- (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; if (self.searchString) { CGRect rect = [self.textView frameOfTextRange:[self.textView.text rangeOfString:self.searchString]]; [self.textView scrollRangeToVisible:[self.textView.text rangeOfString:self.searchString]]; UIView *markerView = [[UIView alloc] initWithFrame:rect]; markerView.backgroundColor = [UIColor colorWithRed:0.810 green:0.735 blue:0.000 alpha:0.660]; [self.textView addSubview:markerView]; _searchString = nil; } }
我发现下面的解决scheme工作。 而不是使用firstRectForRange:
它只返回一个矩形,我使用selectionRectsForRange:
CGRectUnion
然后迭代所有矩形,并使用CGRectUnion
来合并它们。
- (CGRect)frameOfTextRange:(NSRange)range { UITextPosition *beginning = self.beginningOfDocument; UITextPosition *start = [self positionFromPosition: beginning offset: range.location]; UITextPosition *end = [self positionFromPosition: start offset: range.length]; UITextRange *textRange = [self textRangeFromPosition: start toPosition: end]; CGRect rect = CGRectZero; NSArray *selectionRects = [self selectionRectsForRange: textRange]; for (UITextSelectionRect *selectionRect in selectionRects) { rect = CGRectUnion(rect, selectionRect.rect); } return rect; }
这在旧项目中使用换行符( '\n'
)很好。
@implementation UITextView (TextFrame) - (CGRect)frameOfTextRange:(NSRange)range { UITextPosition *beginning = self.beginningOfDocument; UITextPosition *start = [self positionFromPosition:beginning offset:range.location]; UITextPosition *end = [self positionFromPosition:start offset:range.length]; UITextRange *textRange = [self textRangeFromPosition:start toPosition:end]; CGRect rect = [self firstRectForRange:textRange]; return [self convertRect:rect fromView:self.textInputView]; } @end
这是我的Swift
翻译
extension UITextView { func frameOfTextRange(range:NSRange) -> CGRect { let beginning = self.beginningOfDocument; let start = self.positionFromPosition(beginning, offset: range.location) let end = self.positionFromPosition(start, offset: range.length) let textRange = self.textRangeFromPosition(start, toPosition: end) let rect = self.firstRectForRange(textRange) return self.convertRect(rect, fromView: self.textInputView) } }
您可以强制self.textView
以确保立即布局:
[self.textView.layoutManager ensureLayoutForTextContainer:self.textView.textContainer];
在你的frameOfTextRange:
call之前放置它会给你正确的方向。
当然你可以在你的frameOfTextRange:
方法中调用它:
- (CGRect)frameOfTextRange:(NSRange)range { [self.layoutManager ensureLayoutForTextContainer:self.textContainer]; ...
这使得frameOfTextRange:
方法可以从任何地方安全地调用。
viewDidLoad:
在从nib或loadView
方法加载视图后调用。 这个容器的大小没有调整,直到后来才被放置。
如果你正在寻找最好的地方,它似乎是-[UIViewController viewDidLayoutSubviews]
方法:
- (void)viewDidLayoutSubviews { if (self.searchString) { CGRect rect = [self.textView frameOfTextRange:[self.textView.text rangeOfString:self.searchString]]; [self.textView scrollRangeToVisible:[self.textView.text rangeOfString:self.searchString]]; UIView *markerView = [[UIView alloc] initWithFrame:rect]; markerView.backgroundColor = [UIColor colorWithRed:0.810 green:0.735 blue:0.000 alpha:0.660]; [self.textView addSubview:markerView]; _searchString = nil; } }
你也可能想要添加一个属性来保存“marketView”,这样如果它再次布局(回应轮换或其他),它可以被重新定位,而不是添加一个新的视图。
viewDidLoad
控制器视图由于父视图控制器的可用大小,状态栏状态等尚未应用,因此具有任意的非确定框架。
在viewWillAppear
,当加载视图添加到父控制器/窗口视图层次结构并且其frame
是“确定的”时,精确框架计算的最佳位置就是viewWillAppear
。