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