解决NSTextContainer分页时文本截断问题

解决NSTextContainer分页时文本截断问题

NSTextContainer与NSLayoutManager配合使用可以将大文本文件分页,但是,分页过程中会遇到问题,显示字符被截断的问题:)

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // 数据源
    NSString *string = [NSString stringWithContentsOfURL:[NSBundle.mainBundle URLForResource:@"bubizhidaowoshishui" withExtension:@"txt"] usedEncoding:nil
                                                   error:nil];
    
    // 文本容器
    NSTextStorage *storage = [[NSTextStorage alloc] initWithString:string];
    
    // 文本容器的布局管理器
    NSLayoutManager *layoutManager = [NSLayoutManager new];
    [storage addLayoutManager:layoutManager];
    
    // 分段显示文本容器中的内容
    CGSize size = CGSizeMake(300, 540);
    NSTextContainer *textContainer1 = [[NSTextContainer alloc] initWithSize:size];
    [layoutManager addTextContainer:textContainer1];
    
    
    NSTextContainer *textContainer2 = [[NSTextContainer alloc] initWithSize:size];
    [layoutManager addTextContainer:textContainer2];

    
    NSTextContainer *textContainer3 = [[NSTextContainer alloc] initWithSize:size];
    [layoutManager addTextContainer:textContainer3];
    
    
    UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(10, 20,
                                                                        size.width,
                                                                        size.height)
                                               textContainer:textContainer3];

    textView.layer.borderWidth = 1;
    textView.scrollEnabled     = NO;
    textView.editable          = NO;
    [self.view addSubview:textView];
}

以下是我的运行结果(注意看底下红色的部分,文本被截断了哦):

为什么会被截断呢,按理说,NSLayoutManager会计算好一个size值然后给NSTextContainer让这个NSTextContainer自己适应的.

苹果官方文档里面有描述:

Generating Line Fragment Rectangles

The layout manager lays text within an NSTextContainer object in lines of glyphs. The layout of these lines within the text container is determined by its shape and by any exclusion paths it contains. Wherever the line fragment rectangle intersects a region defined by an exclusion path, the lines in those parts must be shortened or fragmented; if there’s a gap across the entire region, the lines that would overlap it have to be shifted to compensate.

The layout manager proposes a rectangle for a given line and then asks the text container to adjust the rectangle to fit. The proposed rectangle usually spans the text container’s bounding rectangle, but it can be narrower or wider, and it can also lie partially or completely outside the bounding rectangle. The message that the layout manager sends the text container to adjust the proposed rectangle is lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect:, which returns the largest rectangle available for the proposed rectangle, based on the direction in which text is laid out. It also returns a rectangle containing any remaining space, such as the space left on the other side of a hole or gap in the text container.

The layout manager makes one final adjustment when it actually fits text into the rectangle. This adjustment is a small amount fixed by the text container, called the line fragment padding, which defines the portion on each end of the line fragment rectangle left blank. Text is inset within the line fragment rectangle by this amount (the rectangle itself is unaffected). Padding allows for small-scale adjustment of the text container’s region at the edges (and around any holes) and keeps text from directly abutting any other graphics displayed near the region. You can change the padding from its default value with the lineFragmentPadding property. Note that line fragment padding isn’t a suitable means for expressing margins. For document margins, you should set the UITextView object’s position and size within its enclosing view. And for text margins, you should set the textContainerInset property of the text view. In addition, you can set indentation values for individual paragraphs using NSMutableParagraphStyle properties such as headIndent.

In addition to returning the line fragment rectangle itself, the layout manager returns a rectangle called the used rectangle. This is the portion of the line fragment rectangle that actually contains glyphs or other marks to be drawn. By convention, both rectangles include the line fragment padding and the interline space (which is calculated from the font’s line height metrics and the paragraph’s line spacing parameters). However, the paragraph spacing (before and after) and any space added around the text, such as that caused by center-spaced text, are included only in the line fragment rectangle, and are not included in the used rectangle.

每个NSTextContainer的frame值都是被NSLayoutManager粗略计算过的,与你设置NSTextContainer的size值略有出入,有时候大些,有时候小些,但误差绝度不会超过一个字符的高度.所以,苹果建议我们在设置UITextView的时候,给这个NSTextContainer预留一定的高度......

解决的方法如下:

效果如下:

这个问题有这么棘手么?其实,我是在黔驴技穷的情况下(github上下载了7-8个相关demo,stackoverflow上搜寻等等途径都无效的情况下)细致研究官方提供的pdf文档才明白过来的:),你懂的.

附录:

使用自定义字体不是梦:)

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // 数据源
    NSString *string = [NSString stringWithContentsOfURL:[NSBundle.mainBundle URLForResource:@"bubizhidaowoshishui" withExtension:@"txt"] usedEncoding:nil
                                                   error:nil];
    
    // 文本容器
    NSTextStorage *storage = [[NSTextStorage alloc] initWithString:string];
    
    // 文本容器的布局管理器
    NSLayoutManager *layoutManager = [NSLayoutManager new];
    [storage addLayoutManager:layoutManager];
    
    
    // 段落属性
    NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
    paragraphStyle.lineHeightMultiple  = 1.f;                    // 可变行高,乘因数
    paragraphStyle.lineSpacing         = 5.f;                    // 行间距
    paragraphStyle.minimumLineHeight   = 10.f;                   // 最小行高
    paragraphStyle.maximumLineHeight   = 20.f;                   // 最大行高
    paragraphStyle.paragraphSpacing    = 10.f;                   // 段间距
    paragraphStyle.alignment           = NSTextAlignmentLeft;    // 对齐方式
    paragraphStyle.firstLineHeadIndent = 30.f;                   // 段落首文字离边缘间距
    paragraphStyle.headIndent          = 0.f;                    // 段落除了第一行的其他文字离边缘间距
    paragraphStyle.tailIndent          = 0.f;                    // ???????
    [storage addAttribute:NSParagraphStyleAttributeName
                    value:paragraphStyle
                    range:NSMakeRange(0, storage.string.length)];
    
    // 字体属性
    [storage addAttribute:NSFontAttributeName
                    value:[UIFont fontWithName:CUSTOM_FONT(@"新蒂小丸子体", 0) size:15.f]
                    range:NSMakeRange(0, storage.string.length)];
    
    [storage addAttribute:NSForegroundColorAttributeName
                    value:[UIColor redColor]
                    range:NSMakeRange(0, storage.string.length)];
    
    
    // 分段显示文本容器中的内容
    CGSize size = CGSizeMake(300, 520);
    NSTextContainer *textContainer1 = [[NSTextContainer alloc] initWithSize:size];
    [layoutManager addTextContainer:textContainer1];
    
    
    NSTextContainer *textContainer2 = [[NSTextContainer alloc] initWithSize:size];
    [layoutManager addTextContainer:textContainer2];

    
    NSTextContainer *textContainer3 = [[NSTextContainer alloc] initWithSize:size];
    [layoutManager addTextContainer:textContainer3];
    
    
    UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake(10, 20,
                                                                        size.width,
                                                                        size.height + 20)
                                               textContainer:textContainer3];

    textView.layer.borderWidth = 1;
    textView.scrollEnabled     = NO;
    textView.editable          = NO;
    [self.view addSubview:textView];
}
原文地址:https://www.cnblogs.com/YouXianMing/p/3770873.html