iOS文本布局探讨之一——文本布局框架TextKit浅析

简介: 在iOS开发中,使用TextKit框架进行富文本布局。TextKit有一组高级的类和协议组成,其中使用比较上层的API来完成复杂的富文本布局,功能十分强大。

iOS文本布局探讨之一——文本布局框架TextKit浅析

一、引言

        在iOS开发中,处理文本的视图控件主要有4中,UILabel,UITextField,UITextView和UIWebView。其中UILabel与UITextField相对简单,UITextView是功能完备的文本布局展示类,通过它可以进行复杂的富文本布局,UIWebView主要用来加载网页或者pdf文件,其可以进行HTML,CSS和JS等文件的解析。

        TextKit是一个偏上层的开发框架,在iOS7以上可用,使用它开发者可以方便灵活处理复杂的文本布局,满足开发中对文本布局的各种复杂需求。TextKit实际上是基于CoreText的一个上层框架,其是面向对象的,如果TextKit中提供的API无法满足需求,可以使用CoreText中的API进行更底层的开发。

        官方文档中的一张图片很确切,经常会被用来描述TextKit框架在iOS系统文本渲染中所处的位置。

二、TextKit框架的结构

        界面在进行文本的渲染时,有下面几个必要条件:

1.要渲染展示的内容。

2.将内容渲染在某个视图上。

3.内容渲染在视图上的尺寸位置和形状。

在TextKit框架中,提供了几个类分别对应处理上述的必要条件:

1.NSTextStorage对应要渲染展示的内容。

2.UITextView对应要渲染的视图。

3.NSTextContainer对应渲染的尺寸位置和形状信息。

除了上述3个类之外,TextKit框架中的NSLayoutManager类作为协调者来进行布局操作。

上述关系如下图所示:

三、使用TextKit进行文本布局流程

        个人理解,TextKit主要用于更精细的处理文本布局以及进行复杂的图文混排布局,使用TextKit进行文本的布局展示十分繁琐,首先需要将显示内容定义为一个NSTextStorage对象,之后为其添加一个布局管理器对象NSLayoutManager,在NSLayoutManager中,需要进行NSTextContainer的定义,定义多了NSTextContainer对象则会将文本进行分页。最后,将要展示的NSTextContainer绑定到具体的UITextView视图上。

示例代码如下:

    //定义Container
    NSTextContainer * container = [[NSTextContainer alloc]initWithSize:CGSizeMake(150, 200)];
    //定义布局管理类
    NSLayoutManager * layoutManager = [[NSLayoutManager  alloc]init];
    //将container添加进布局管理类管理
    [layoutManager addTextContainer:container];
    //定义一个Storage
    NSTextStorage * storage = [[NSTextStorage alloc]initWithString:@"The NSTextContainer class defines a region where text is laid out. An NSLayoutManager uses NSTextContainer to determine where to break lines, lay out portions of text, and so on."];
    //为Storage添加一个布局管理器
    [storage addLayoutManager:layoutManager];
    //将要显示的container与视图TextView绑定
    UITextView * textView = [[UITextView alloc]initWithFrame:self.view.frame textContainer:container];
    [self.view addSubview:textView];

上面代码演示的过程如下图所示:

需要注意,TextKit进行布局的核心思路是最终的视图对应一个文本块Container,并不是一段文本内容Storage,LayoutManager会将完整的内容根据其中Container的尺寸进行分页,TextView根据需要显示的部分进行Container的选择。

四、了解NSTextContainer类

        NSTextContainer可以简单理解为创建一个文本区块,文本内容将在这个区块中进行渲染,其中常用属性与方法如下:

//初始化方法 设置区块的尺寸
- (instancetype)initWithSize:(CGSize)size;
//与其绑定的layoutManager 需要注意,不是设置这个属性 使用[NSLayoutManager addTextContainer:]方式来进行绑定
@property(nullable, assign, NS_NONATOMIC_IOSONLY) NSLayoutManager *layoutManager;
//替换绑定的布局管理类对象
- (void)replaceLayoutManager:(NSLayoutManager *)newLayoutManager;
//获取区块尺寸
@property(NS_NONATOMIC_IOSONLY) CGSize size;
//设置从区块中剔除某一区域
@property(copy, NS_NONATOMIC_IOSONLY) NSArray<UIBezierPath *> *exclusionPaths;
//设置截断模式 需要注意 这个属性的设置只是会影响此区块的最后一行的截断模式
@property(NS_NONATOMIC_IOSONLY) NSLineBreakMode lineBreakMode;
//设置每行文本左右空出的间距
@property(NS_NONATOMIC_IOSONLY) CGFloat lineFragmentPadding;
//设置TextView上可输入的文本最大行数
@property(NS_NONATOMIC_IOSONLY) NSUInteger maximumNumberOfLines;

//这个方法用于提供给子类进行重写 这里返回的Rect是可以布局文本的区域
- (CGRect)lineFragmentRectForProposedRect:(CGRect)proposedRect atIndex:(NSUInteger)characterIndex writingDirection:(NSWritingDirection)baseWritingDirection remainingRect:(nullable CGRect *)remainingRect;
//这个BOOL值的属性决定Container的宽度是否自适应TextView的宽度
@property(NS_NONATOMIC_IOSONLY) BOOL widthTracksTextView;
//这个BOOL值的属性决定Container的高度是否自适应TextView的高度
@property(NS_NONATOMIC_IOSONLY) BOOL heightTracksTextView;

上面所列举的方法中,exclusionPaths属性十分强大,通过设置它,可以将布局区域内剔出一块区域不进行布局,示例代码如下:

 [super viewDidLoad];
    NSTextContainer * container = [[NSTextContainer alloc]initWithSize:CGSizeMake(300, 500)];

    UIBezierPath * path = [UIBezierPath bezierPathWithArcCenter:self.view.center radius:70 startAngle:0 endAngle:M_PI*2 clockwise:YES];
    container.exclusionPaths = @[path];
    container.lineBreakMode = NSLineBreakByCharWrapping;
    NSLayoutManager * layoutManager = [[NSLayoutManager  alloc]init];
    [layoutManager addTextContainer:container];
    NSTextStorage * storage = [[NSTextStorage alloc]initWithString:@"The NSTextContainer class defines a region where text is laid out. An NSLayoutManager uses NSTextContainer to determine where to break lines, lay out portions of text, and so on. An NSTextContainer object normally defines rectangular regions, but you can define exclusion paths inside the text container to create regions where text does not flow. You can also subclass to create text containers with nonrectangular regions, such as circular regions, regions with holes in them, or regions that flow alongside graphics.The NSTextContainer class defines a region where text is laid out. An NSLayoutManager uses NSTextContainer to determine where to break lines, lay out portions of text, and so on. An NSTextContainer object normally defines rectangular regions, but you can define exclusion paths inside the text container to create regions where text does not flow. You can also subclass to create text containers with nonrectangular regions, such as circular regions, regions with holes in them, or regions that flow alongside graphics.An NSLayoutManager uses NSTextContainer to determine where to break lines, lay out portions of text, and so on. An NSTextContainer object normally defines rectangular regions, but you can define exclusion paths inside the text container to create regions where text does not flow. You can also subclass to create text containers with nonrectangular regions, such as circular regions, regions with holes in them, or regions that flow alongside graphics."];
    [storage addLayoutManager:layoutManager];
    UITextView * textView = [[UITextView alloc]initWithFrame:self.view.frame textContainer:container];
    [self.view addSubview:textView];

效果如下图:

四、关于NSLayoutManager

        顾名思义,NSLayoutManager专门负责对文本的布局渲染,简单理解,其从NSTextStorage从拿去展示的内容,将去处理后布局到NSTextContainer中。

        NSLayoutManager与NSTextContainer的关系为一对多,放入NSLayoutManager中的NSTextContainer会以有序数组的形式进行管理,在内容布局时,超出第一个NSTextContainer的内容会被布局到后一个NSTextContainer中。

        NSLayoutManager中有关NSTextContainer操作的方法如下:

//container数组
@property(readonly, NS_NONATOMIC_IOSONLY) NSArray<NSTextContainer *> *textContainers;
//添加一个container
- (void)addTextContainer:(NSTextContainer *)container;
//在指定位置插入一个container
- (void)insertTextContainer:(NSTextContainer *)container atIndex:(NSUInteger)index;
//删除一个指定的container
- (void)removeTextContainerAtIndex:(NSUInteger)index;
//注意 这个方法不需要显式的调用 当布局Container发生变化时 系统会自动调用
- (void)textContainerChangedGeometry:(NSTextContainer *)container;

与布局管理相关的属性与方法如下:

//是否显示隐形的符号
/*
默认为NO,如果设置为YES,则会将空格等隐形字符显示出来
*/
@property(NS_NONATOMIC_IOSONLY) BOOL showsInvisibleCharacters;
//是否显示某些布局控制字符
@property(NS_NONATOMIC_IOSONLY) BOOL showsControlCharacters;
//这个属性可以用于设置断字
/*
这个属性的取值为0到1之间 默认为0 即单词换行时从来不会中断 越接近1 则使用连字符进行单词换行中断的概率越大
*/
@property(NS_NONATOMIC_IOSONLY) CGFloat hyphenationFactor;
//是否使用字体定义的行距
/*
默认使用字体所定义的行距信息 通过设置这个属性为NO可以关闭此功能
*/
@property(NS_NONATOMIC_IOSONLY) BOOL usesFontLeading;
//这个属性设置是否允许对相邻位置的内容进行布局 默认为YES,设置为NO后将可以提供大文本布局的效率
@property(NS_NONATOMIC_IOSONLY) BOOL allowsNonContiguousLayout;

//下面这几个方法用于移除某一范围内的布局
- (void)invalidateGlyphsForCharacterRange:(NSRange)charRange changeInLength:(NSInteger)delta actualCharacterRange:(nullable NSRangePointer)actualCharRange;
- (void)invalidateLayoutForCharacterRange:(NSRange)charRange actualCharacterRange:(nullable NSRangePointer)actualCharRange NS_AVAILABLE(10_5, 7_0);
- (void)invalidateDisplayForCharacterRange:(NSRange)charRange;
- (void)invalidateDisplayForGlyphRange:(NSRange)glyphRange;

五、文本内容类NSTextStorage

        NSTextStorage实际上是继承自NSMutableAttributedString。NSAttributedString是一种自带属性的字符串类,关于NSAttributedString的基本用法,如下博客中有介绍:

http://my.oschina.net/u/2340880/blog/397500

        TextKit框架中在对文本进行布局时,主要关注于3个方面:

1.字符的属性,例如颜色,字体等。

2.行与段落的属性,如缩进,行间距等。

3.文档属性,包括四周边距、文档尺寸等。

这些都由NSAttributedString来进行定义。

        如上所介绍的是TextKit框架的主要工作原理,文字渲染,图文混排的更多内容,后面博客会继续探讨。有疏漏之处,共同讨论进步。

专注技术,热爱生活,交流技术,也做朋友。

——珲少 QQ群:203317592

目录
相关文章
|
2月前
|
物联网 区块链 vr&ar
未来已来:探索区块链、物联网与虚拟现实技术的融合与应用安卓与iOS开发中的跨平台框架选择
【8月更文挑战第30天】在科技的巨轮下,新技术不断涌现,引领着社会进步。本文将聚焦于当前最前沿的技术——区块链、物联网和虚拟现实,探讨它们各自的发展趋势及其在未来可能的应用场景。我们将从这些技术的基本定义出发,逐步深入到它们的相互作用和集成应用,最后展望它们如何共同塑造一个全新的数字生态系统。
|
3月前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台框架解析
在移动应用开发的广阔舞台上,安卓和iOS一直是两大主角。随着技术的进步,开发者们渴望能有一种方式,让他们的应用能同时在这两大平台上运行,而不必为每一个平台单独编写代码。这就是跨平台框架诞生的背景。本文将探讨几种流行的跨平台框架,包括它们的优势、局限性,以及如何根据项目需求选择合适的框架。我们将从技术的深度和广度两个维度,对这些框架进行比较分析,旨在为开发者提供一个清晰的指南,帮助他们在安卓和iOS的开发旅程中,做出明智的选择。
|
3月前
|
开发工具 Swift iOS开发
探索iOS开发中的SwiftUI框架
在数字时代的浪潮中,iOS应用开发的舞台日益扩展,其中SwiftUI作为苹果推出的新型用户界面框架,正逐渐改变着开发者构建应用的方式。本文将深入介绍SwiftUI的核心概念和实际应用,探讨其如何简化代码、提升效率并推动设计创新,同时也会触及SwiftUI在当前技术生态中所面临的挑战与未来的发展潜力。
|
3月前
|
机器学习/深度学习 API iOS开发
探索iOS开发中的SwiftUI框架深入理解RESTful API设计原则与最佳实践
【7月更文挑战第30天】本文深入探讨了SwiftUI框架在iOS开发中的应用,分析了其对用户界面构建的简化方法及性能优化。通过比较传统UI构建方式与SwiftUI的差异,揭示了SwiftUI如何提高开发效率和用户体验。文章还讨论了SwiftUI在实际项目中的集成策略,并展望了其未来的发展方向。 【7月更文挑战第30天】在数字时代的浪潮中,RESTful API如同一座桥梁,连接着不同的软件系统。本文将探讨RESTful API的核心设计原则,揭示其背后的哲学思想,并通过实例分析展示如何将这些原则应用于实际开发中。我们将从资源定位、接口一致性到HTTP方法的恰当使用,逐一剖析,旨在为开发者提供
51 1
|
6天前
|
iOS开发 开发者 UED
探索iOS应用开发中的SwiftUI框架
【9月更文挑战第26天】 在iOS开发的海洋中,SwiftUI犹如一艘现代的快艇,引领着开发者们驶向更加高效与直观的编程体验。本文将带你领略SwiftUI的魅力,从其设计理念到实际应用,我们将一步步揭开它如何简化界面构建过程的面纱。通过对比传统方式,你将看到SwiftUI如何让代码变得像诗一样优美,同时保持强大的功能性和灵活性。准备好让你的iOS开发技能加速升级,一起驾驭这股新潮流吧!
|
12天前
|
前端开发 iOS开发 开发者
探索iOS开发中的SwiftUI框架
【9月更文挑战第21天】在iOS应用开发的广阔天地中,SwiftUI框架如一股清新之风,为开发者带来了声明式语法的便捷与高效。本文将深入探讨SwiftUI的核心概念、布局方式及数据绑定机制,同时通过实例演示如何运用SwiftUI构建用户界面,旨在引领读者领略SwiftUI的魅力,并激发其对iOS开发新趋势的思考与实践。
30 6
|
2月前
|
机器学习/深度学习 搜索推荐 数据处理
探索iOS应用开发的新趋势:SwiftUI和Combine框架
【8月更文挑战第6天】随着Apple不断推动其操作系统的进化,iOS开发领域也迎来了新的变革。本文将深入探讨SwiftUI和Combine框架如何革新iOS应用开发流程,提升开发者的工作效率,并改善最终用户的体验。我们将从这两个框架的基本概念出发,分析它们的核心优势,并预测它们将如何塑造iOS开发的未来。
|
22天前
|
安全 iOS开发
iOS页面布局:UIScrollView的布局问题
iOS页面布局:UIScrollView的布局问题
|
1月前
|
开发工具 Swift iOS开发
探索iOS开发中的SwiftUI框架
【9月更文挑战第1天】在本文中,我们将一起潜入iOS开发的海洋,特别聚焦于SwiftUI这一现代且富有表现力的框架。SwiftUI不仅简化了界面设计流程,还为开发者提供了声明式Swift语法的便利。通过这篇文章,你将学会如何利用SwiftUI构建灵活且响应式的用户界面,并理解其背后的原理。无论你是刚入门的新手还是寻求进阶的开发者,本文都将为你提供有价值的指导和启示。
|
2月前
|
设计模式 Java Android开发
探索安卓应用开发:从新手到专家的旅程探索iOS开发中的SwiftUI框架
【8月更文挑战第29天】本文旨在通过一个易于理解的旅程比喻,带领读者深入探讨安卓应用开发的各个方面。我们将从基础概念入手,逐步过渡到高级技术,最后讨论如何维护和推广你的应用。无论你是编程新手还是有经验的开发者,这篇文章都将为你提供有价值的见解和实用的代码示例。让我们一起开始这段激动人心的旅程吧!