修改
简单的解决方案是将任何帧计算从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]];
...
}
}
答案 0 :(得分:5)
我找到了以下解决方案。我使用firstRectForRange:
,而不是使用只返回一个矩形的selectionRectsForRange:
,然后遍历所有rects并使用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;
}
答案 1 :(得分:3)
显然似乎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;
}
}
答案 2 :(得分:2)
对于我来说,旧项目中的换行符('\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)
}
}
答案 3 :(得分:2)
您可以强制self.textView
立即确保布局:
[self.textView.layoutManager ensureLayoutForTextContainer:self.textView.textContainer];
在frameOfTextRange:
来电之前放置它会给你正确的矩阵。
当然,您可以在frameOfTextRange:
方法中调用它:
- (CGRect)frameOfTextRange:(NSRange)range {
[self.layoutManager ensureLayoutForTextContainer:self.textContainer];
...
这样可以从任何地方安全地调用frameOfTextRange:
方法。
答案 4 :(得分:1)
viewDidLoad:
方法加载视图后立即调用 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',这样如果它再次布局(响应旋转或其他内容),可以重新定位它而不是添加新视图。
答案 5 :(得分:0)
viewDidLoad
中的控制器视图具有任意的非确定帧,因为父视图控制器的可用大小,状态栏状态等尚未应用。
进行精确帧计算的最佳位置是viewWillAppear
,即加载的视图已添加到父控制器/窗口视图层次结构且其frame
是“确定的”。
答案 6 :(得分:0)
在 Swift 中对我有用的解决方案基于 Koen's solution,但有一个关键区别。我没有根据选定的范围计算 UITextRange
,而是使用 selectedTextRange 属性。此属性已返回为所选文本范围计算的 UITextRange
。
Swift 5.3(经测试可能适用于较低版本):
import UIKit
extension UITextView {
/**
Returns selected text frame position
*/
func frameOfSelectedTextRange() -> CGRect? {
guard let selectedTextRange = self.selectedTextRange,
!selectedTextRange.isEmpty else { return nil }
let rects = self.selectionRects(for: selectedTextRange)
let rect = rects.reduce(into: CGRect.zero) { (res: inout CGRect, next: UITextSelectionRect) in
res = res.union(next.rect)
}
return rect
}
}
注意 B.S's solution 使用 firstRectForRange
返回给我我所选文本的错误帧位置。