在UITextView中查找可见文本的范围

时间:2014-08-08 22:13:12

标签: ios uitextview nsattributedstring

我找到了以前问题的答案代码,用于计算UITextView中可见属性文本的范围。它完美地工作,除非我添加任何类型的换行符以创建新段落。在这种情况下,代码给出了错误的答案,直到我手动滚动视图。滚动后,每次都会给出正确的答案。但即使不滚动,我也需要它给出正确的答案。

以下是我的示例项目。它是单一视图应用程序。该程序在日志记录中报告它认为可见的文本,第一次加载视图后,然后滚动后。

ViewController.h

#import <UIKit/UIKit.h>
@interface ViewController : UIViewController <UIScrollViewDelegate> {
    UITextView *lTextView;
}
@end

ViewController.m

#import "ViewController.h"

@interface ViewController ()


@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    int statusBarHeight = 20;

    UIScrollView *scrollView;
    scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, statusBarHeight, self.view.frame.size.width * 2, self.view.frame.size.height)];
    scrollView.pagingEnabled = YES;
    scrollView.delegate = self;
    [self.view addSubview:scrollView];

    lTextView = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, scrollView.frame.size.width/2, scrollView.frame.size.height)];
    lTextView.editable = NO;
    lTextView.selectable = NO;

    lTextView.attributedText = [self loremIpsum];
    [scrollView addSubview:lTextView];

    lTextView.delegate = self;

    [self report];
}

- (void)report {
    NSArray *visibleRange = [self visibleRangeOfTextView:lTextView];
    NSNumber *start = [visibleRange objectAtIndex:0];
    NSNumber *end = [visibleRange objectAtIndex:1];
    int rangeLength = (end.intValue - start.intValue);

    NSLog(@"%@", [lTextView.text substringWithRange:NSMakeRange(start.intValue, rangeLength)]);

}


-(NSArray *)visibleRangeOfTextView:(UITextView *)textView {
    CGRect bounds = textView.bounds;
    bounds.size.height -= 30.0; // to prevent including lines that can barely be seen at the bottom

    UITextPosition *start = [textView characterRangeAtPoint:bounds.origin].start;
    UITextPosition *end = [textView characterRangeAtPoint:CGPointMake(CGRectGetMaxX(bounds), CGRectGetMaxY(bounds))].end; // <<<< this gives an incorrect calculation until the user manually scrolls

    float startOffset = [textView offsetFromPosition:textView.beginningOfDocument toPosition:start];
    float endOffset = [textView offsetFromPosition:textView.beginningOfDocument toPosition:end];
    NSLog(@"startOffset: %f, endOffset %f", startOffset, endOffset);

    return [NSArray arrayWithObjects:[NSNumber numberWithFloat:startOffset], [NSNumber numberWithFloat:endOffset], nil];

}


- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    NSLog(@"scrollViewDidEndDecelerating");
    [self report];
}


- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    NSLog(@"scrollViewDidEndDragging");
    [self report];
}


- (NSAttributedString *)loremIpsum {

    NSMutableAttributedString *li = [[NSMutableAttributedString alloc] init];
    NSArray *strings = [NSArray arrayWithObjects:@"1 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam congue eleifend risus eget pretium. Donec sed commodo neque, id ornare dolor.", @" 2 Vivamus vestibulum non quam et euismod. Morbi et dolor luctus velit lobortis ornare vel vel tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean viverra, urna nec tempor commodo, turpis nisl rhoncus mauris, in ullamcorper justo sapien quis nulla.", @" 3 Aliquam at odio molestie, laoreet elit sed, suscipit risus. Nulla eleifend, quam eget porttitor condimentum, metus lacus lobortis ligula, accumsan tristique neque turpis non purus. Aenean malesuada tortor id elit semper, et pretium nulla viverra.", @" 4 Aliquam sollicitudin placerat massa, quis posuere est ornare vel. Nam mollis convallis risus a tincidunt. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.", @" 5 Fusce facilisis orci nisi, vel egestas metus tristique vitae. Proin nec malesuada dolor. Donec eget felis accumsan, facilisis turpis vitae, blandit lacus.", @" 6 Pellentesque auctor nisl quis turpis commodo lacinia. In sed euismod urna. Praesent sed commodo magna.", @" 7 Ut interdum dignissim urna, nec feugiat dolor. Nulla facilisi. Donec fermentum mauris at ante tincidunt, id accumsan eros lacinia.", @" 8 Suspendisse potenti. Integer ac mattis eros, sed volutpat dui. Pellentesque vehicula turpis ut metus malesuada blandit.", @" 9 Nam laoreet dui id imperdiet pulvinar. In auctor enim ac massa feugiat adipiscing. Nam convallis neque at felis tincidunt iaculis. Maecenas dictum est ac nulla suscipit, nec condimentum metus molestie.", @" 10 Vestibulum mollis velit eu nunc eleifend egestas. Ut aliquam ultrices tellus volutpat consectetur. Morbi eget sollicitudin quam, ut imperdiet leo. Morbi sed ligula iaculis, tincidunt diam nec, pharetra ligula.", @" 11 Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Proin varius facilisis placerat. Fusce suscipit risus risus, in varius diam convallis quis. In hac habitasse platea dictumst. Integer non lectus non dolor fringilla venenatis eget quis nisl. Proin in pretium metus.", @" 12 Pellentesque sed tellus iaculis, bibendum neque vel, porta ante. Phasellus eu vulputate massa. Nullam venenatis lectus non nunc aliquet porta. Nunc gravida rutrum feugiat. Fusce elit nunc, facilisis non tristique placerat, tempor id orci. Mauris et massa cursus, dapibus urna a, condimentum arcu. Pellentesque vitae sagittis sapien. Ut lacus purus, suscipit at magna non, rhoncus luctus dui. Ut ipsum augue, pharetra ac ipsum sed, facilisis convallis justo.", @" 13 Ut arcu augue, hendrerit vel tincidunt vitae, aliquet ac quam. Nulla ullamcorper, dolor eu pellentesque cursus, lectus quam interdum ante, nec congue dui augue nec dolor. Sed convallis elit in enim dictum, at posuere sem mollis. Praesent in metus aliquam, ullamcorper purus tempor, mattis ipsum. Aliquam gravida, sem vitae iaculis placerat, dui velit commodo nulla, vitae ultrices lectus dolor ut mi.", @" 14 Suspendisse quis metus varius, congue turpis vitae, viverra nunc. Duis placerat, felis et laoreet pretium, nibh lorem pulvinar turpis, eu euismod arcu libero at mauris. Sed laoreet, eros in tempor accumsan, odio augue fermentum dui, a pharetra felis libero eu ligula.", @"Suspendisse ultricies pulvinar urna. Donec placerat nulla non elit vestibulum mattis.", @"Phasellus semper sem a sem dignissim tempus. Sed scelerisque sed purus interdum rhoncus. Sed gravida eros sit amet dui fermentum rutrum. Proin a arcu scelerisque, volutpat orci quis, dignissim neque. Aliquam erat volutpat. Sed eget scelerisque neque.", nil];
    for (int i = 0;  i < strings.count;  i++) {
        NSString *string = [strings objectAtIndex:i];
        if (i != 0) {
            // Comment this line and it will work perfectly:
            [li appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n\n"]];
        }
        [li appendAttributedString:[[NSAttributedString alloc] initWithString:string]];
    }
    return li;
}
@end

感谢您的帮助!

5 个答案:

答案 0 :(得分:3)

我在添加赏金的那天想出了一个解决方案,但没有发布,以便其他人有机会发布对他人有用的东西。 Bajorek先生和rintaro先生都发布了有效的答案。我的解决方案有点不同;它更适合我在我的项目中尝试做的事情(因为我将使用多个UITextViews)。它禁用UITextView中的滚动,让UIScrollView处理滚动,并添加UITextView以满足所有属性文本所需的全部高度。这使得计算从一开始就能正常工作。

为了以后可能会阅读的其他人的利益,我在下面发布了一个完整的工作示例。我将此标记为已接受的答案,因为这是我在发布其他解决方案之前的路线。

ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UIScrollViewDelegate> {
    UITextView *lTextView;
    UIScrollView *scrollView;
}
@end

ViewController.m

#import "ViewController.h"

@interface ViewController ()


@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    int statusBarHeight = 20;

    scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, statusBarHeight, self.view.frame.size.width, self.view.frame.size.height)];
    [self.view addSubview:scrollView];

    lTextView = [[UITextView alloc] init];
    lTextView.editable = NO;
    lTextView.selectable = NO;
    lTextView.scrollEnabled = NO;
    lTextView.textContainerInset = UIEdgeInsetsZero;

    lTextView.attributedText = [self loremIpsum];
    float calculatedHeight = [self heightForTextViewWithAttributedText:lTextView andWidth:scrollView.frame.size.width];
    lTextView.frame = CGRectMake(0, 0, scrollView.frame.size.width, calculatedHeight);
    [scrollView addSubview:lTextView];

    [scrollView setContentSize:CGSizeMake(scrollView.frame.size.width, calculatedHeight + 20)];
    scrollView.delegate = self;

    [self report];
}

- (void)report {
    NSArray *visibleRange = [self visibleRangeOfTextView:lTextView];
    NSNumber *start = [visibleRange objectAtIndex:0];
    NSNumber *end = [visibleRange objectAtIndex:1];
    int rangeLength = (end.intValue - start.intValue);

    NSLog(@"%@", [lTextView.text substringWithRange:NSMakeRange(start.intValue, rangeLength)]);

}


-(NSArray *)visibleRangeOfTextView:(UITextView *)textView {
    double offsetY = scrollView.contentOffset.y;
    CGPoint startingPoint = scrollView.contentOffset;
    startingPoint.y += 5; // to prevent including lines that can barely be seen at the top
    CGPoint endingPoint = CGPointMake(320, offsetY + scrollView.frame.size.height - 20);

    CGRect bounds = textView.bounds;
    bounds.size.height -= 30.0; // to prevent including lines that can barely be seen at the bottom

    UITextPosition *start = [textView characterRangeAtPoint:startingPoint].start;
    UITextPosition *end = [textView characterRangeAtPoint:endingPoint].end;

    float startOffset = [textView offsetFromPosition:textView.beginningOfDocument toPosition:start];
    float endOffset = [textView offsetFromPosition:textView.beginningOfDocument toPosition:end];

    return [NSArray arrayWithObjects:[NSNumber numberWithFloat:startOffset], [NSNumber numberWithFloat:endOffset], nil];
}


- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    NSLog(@"scrollViewDidEndDecelerating");
    [self report];
}


- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    NSLog(@"scrollViewDidEndDragging");
    [self report];
}


- (NSAttributedString *)loremIpsum {

    NSMutableAttributedString *li = [[NSMutableAttributedString alloc] init];
    NSArray *strings = [NSArray arrayWithObjects:@"1 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam congue eleifend risus eget pretium. Donec sed commodo neque, id ornare dolor.", @" 2 Vivamus vestibulum non quam et euismod. Morbi et dolor luctus velit lobortis ornare vel vel tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean viverra, urna nec tempor commodo, turpis nisl rhoncus mauris, in ullamcorper justo sapien quis nulla.", @" 3 Aliquam at odio molestie, laoreet elit sed, suscipit risus. Nulla eleifend, quam eget porttitor condimentum, metus lacus lobortis ligula, accumsan tristique neque turpis non purus. Aenean malesuada tortor id elit semper, et pretium nulla viverra.", @" 4 Aliquam sollicitudin placerat massa, quis posuere est ornare vel. Nam mollis convallis risus a tincidunt. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.", @" 5 Fusce facilisis orci nisi, vel egestas metus tristique vitae. Proin nec malesuada dolor. Donec eget felis accumsan, facilisis turpis vitae, blandit lacus.", @" 6 Pellentesque auctor nisl quis turpis commodo lacinia. In sed euismod urna. Praesent sed commodo magna.", @" 7 Ut interdum dignissim urna, nec feugiat dolor. Nulla facilisi. Donec fermentum mauris at ante tincidunt, id accumsan eros lacinia.", @" 8 Suspendisse potenti. Integer ac mattis eros, sed volutpat dui. Pellentesque vehicula turpis ut metus malesuada blandit.", @" 9 Nam laoreet dui id imperdiet pulvinar. In auctor enim ac massa feugiat adipiscing. Nam convallis neque at felis tincidunt iaculis. Maecenas dictum est ac nulla suscipit, nec condimentum metus molestie.", @" 10 Vestibulum mollis velit eu nunc eleifend egestas. Ut aliquam ultrices tellus volutpat consectetur. Morbi eget sollicitudin quam, ut imperdiet leo. Morbi sed ligula iaculis, tincidunt diam nec, pharetra ligula.", @" 11 Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Proin varius facilisis placerat. Fusce suscipit risus risus, in varius diam convallis quis. In hac habitasse platea dictumst. Integer non lectus non dolor fringilla venenatis eget quis nisl. Proin in pretium metus.", @" 12 Pellentesque sed tellus iaculis, bibendum neque vel, porta ante. Phasellus eu vulputate massa. Nullam venenatis lectus non nunc aliquet porta. Nunc gravida rutrum feugiat. Fusce elit nunc, facilisis non tristique placerat, tempor id orci. Mauris et massa cursus, dapibus urna a, condimentum arcu. Pellentesque vitae sagittis sapien. Ut lacus purus, suscipit at magna non, rhoncus luctus dui. Ut ipsum augue, pharetra ac ipsum sed, facilisis convallis justo.", @" 13 Ut arcu augue, hendrerit vel tincidunt vitae, aliquet ac quam. Nulla ullamcorper, dolor eu pellentesque cursus, lectus quam interdum ante, nec congue dui augue nec dolor. Sed convallis elit in enim dictum, at posuere sem mollis. Praesent in metus aliquam, ullamcorper purus tempor, mattis ipsum. Aliquam gravida, sem vitae iaculis placerat, dui velit commodo nulla, vitae ultrices lectus dolor ut mi.", @" 14 Suspendisse quis metus varius, congue turpis vitae, viverra nunc. Duis placerat, felis et laoreet pretium, nibh lorem pulvinar turpis, eu euismod arcu libero at mauris. Sed laoreet, eros in tempor accumsan, odio augue fermentum dui, a pharetra felis libero eu ligula.", @"Suspendisse ultricies pulvinar urna. Donec placerat nulla non elit vestibulum mattis.", @"Phasellus semper sem a sem dignissim tempus. Sed scelerisque sed purus interdum rhoncus. Sed gravida eros sit amet dui fermentum rutrum. Proin a arcu scelerisque, volutpat orci quis, dignissim neque. Aliquam erat volutpat. Sed eget scelerisque neque.", nil];
    for (int i = 0;  i < strings.count;  i++) {
        NSString *string = [strings objectAtIndex:i];
        if (i != 0) {
            // Comment this line and it will work perfectly:
            [li appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n\n"]];
        }
        [li appendAttributedString:[[NSAttributedString alloc] initWithString:string]];
    }
    return li;
}


- (CGFloat)heightForTextViewWithAttributedText:(UITextView *)textView andWidth:(CGFloat)width
{
    CGSize size = [textView sizeThatFits:CGSizeMake(width, FLT_MAX)];
    return size.height + 3.0;
}
@end

答案 1 :(得分:2)

问题是由于过早地试图找出 visibleRange 造成的。

UI更改的实际效果并不总是立即发生。相反,UIKit可以延迟一些工作,直到你完成主线程。

在提供的示例代码中,lTextView被添加到scrollView,然后 visibleRange 在同一执行线程上计算。额外的UIKit工作尚未完成。

异步调度[self report]调用回主线程。 ViewDidLoad将完成,放弃主线程。 UIKit将有机会完成,你现在应该得到正确的 visibleRange

viewDidLoad中,更改:

[self report];

为:

dispatch_async(dispatch_get_main_queue(), ^{
    [self report];
});

答案 2 :(得分:1)

- viewDidLoadself.view实际上还不在视图层次结构中 因此,UITextView&#39; s - drawRect尚未召集 我认为这就是为什么- offsetFromPosition:无法计算出来的原因。

试试这个:

- (void)viewDidAppear:(BOOL)animated {
    NSLog(@"viewDidAppear");
    [self report];
}

如果您确实想立即强制UITextView布局,请尝试:

[[lTextView layoutManager] ensureLayoutForTextContainer:[lTextView textContainer]];

答案 3 :(得分:0)

听起来API中的UI信息不是最新的。滚动后,UI信息会立即更新。您正在寻找的是一种在更改后立即检索新数据的方法。在尝试确定可见属性文本的范围之前,请尝试调用layoutIfNeeded。

答案 4 :(得分:0)

致电&#34;报告&#34;在委托函数&#34; textView:didEndEdited:&#34;

-(void)textView:(UITextView *)textview didEndEdited{
    [self report];}

当然你已经设置了TextView的委托:

self.textView.delegate = self;

并声明代表协议。