字符串渲染

在本期中咱们已经讨论了不少关于字符串不一样的话题,从编码到本地化再到语法分析。但多数状况下,字符串最终仍是须要被绘制到屏幕上供用户查看、交互。这篇文章涵盖了最基本、最好的练习,以及在用户界面上呈现字符串可能遇到的常见陷阱。html

如何将字符串绘制到屏幕上

简单起见,咱们先看看 UIKit 在字符串渲染方面为咱们提供了哪些控件。以后咱们将讨论一下对于字符串的渲染,iOS 和 OS X 系统中有哪些类似和不一样。ios

UIKit 提供了不少能够在屏幕上显示和编辑文本的类。每个类都是为特定使用状况准备的,因此为了不没必要要的问题,为你手上的任务挑选正确的工具是很是重要的。面试

小编这里推荐一个群:691040931 里面有大量的书籍和面试资料,不少的iOS开发者都在里面交流技术

UILabel

UILabel 是将文本绘制到屏幕上最简单的方式。它是 UIView 的一个子类,用来显示少许的只读文本。文本能够被展现在一行或多行,若是文本不能适应指定的空间咱们还可使用不一样的方式裁剪。尽管 label 使用的方式很简单,可是这里有几个技巧仍是值得提一提的。bash

label 默认只显示一行,可是你能够将 numberOfLines 属性设为其余值来改变这一行为。将它设置为一个大于 1 的值,文本的行数将会被限制为这个指定的值;若是设置为 0,则是告诉 label 无论文本占多少行都显示出来。app

经过设置 text 属性,Label 能够显示简单的纯文本,而设置 attributedText 属性则可让 label 显示富文本。当使用纯文本的时候,你可使用 label 的 fonttextColortextAlignmentshadowColor 和 shadowOffset 属性改变它的外观,若是你但愿改变程序内全部 Label 的风格,你也可使用 [UILabel appearance] 这个方法来进行全局的更改。框架

Attributed strings 提供了更加灵活的格式,字符串的不一样部分可使用不一样的格式。让咱们看看常见布局部分,下面给出 attributed strings 一些示例。(下文“常见布局”那一节给出了具体的关于 Attributed String 的一些例子。)工具

除了经过上文提到的那些属性来调整 UILabel 的显示风格外,你还能够经过设置 UILabel 的 adjustsFontSizeToWidthminimumScaleFactoradjustsLetterSpacingToFitWidth 这 3 个 BOOL 值的属性让 UILabel 根据所显示的文本的内容自动地进行调整。若是你很是在乎用户界面的美观,那么你就不要开启这些属性,由于这会使文字的显示效果变得不那么美观,可是有的时候,好比在进行程序不一样语言本土化的时候,你会遇到一些很棘手的问题,除了使用这些选项外很难找到别的解决办法。不信的话,你能够打开 iPhone,在设置中把系统语言改成德语,而后你就会发现苹果官方出品的程序里处处都是被压扁变了形的丑陋不堪的文本。这种处理方法并不完美,但有时却颇有用。布局

若是你使用这些选项让 UIKit 压缩你的文本以适配,若是压缩的时候想让文本保持在同一条基线上或须要对齐到左上角,那么你能够定义 baselineAdjustment 属性。然而,这个选项只对单行 label 起做用。post

当你使用上述方法让文本自动缩放以适配 UILabel 时,你可使用 baselineAdjustment 这个属性来调整缩放时文本是水平对齐仍是对齐到 Label 的左上角。注意,这个属性仅在单行的 Lable (即 numberOfLines 属性值为1时)中生效。字体

UITextField

像 label 同样,text fields 能够处理纯文本或带属性的文本。但 label 只能显示文本而已,text field 还能够处理用户的输入。然而 text field 只限于单行文本。UITextField 是 UIControl 的一个子类,它会**挂钩 (hook into)到响应链,而且当用户开始或结束编辑时分发(deliver)**这些行为消息,若是想要获得更多的控制权,你能够实现 text field 的代理

Text field 有一系列控制文本输入行为的选项。UITextField 实现了 UITextInputTraits 协议,这个协议须要你指定键盘外观和操做的各类细节,好比,须要显示哪一种键盘,返回按钮的响应事件是什么。

当没有文本输入的时候 Text field 还能够显示一个占位符,在右手边显示一个标准的清除按钮,控制任意左右两个辅助视图。你还能够为其设置一个背景图片,这样咱们就能够用一个可变大小的图片为 text field 自定义边框风格了。

但每当你须要输入多行文本的时候,你就须要使用到 UITextField 的大哥了...

UITextView

Text view 是显示或编辑大量文本的理想选择。 UITextView 是 UIScrollView 的一个子类,因此它能容许用户先后滚动达处处理溢出文本的目的。和 text field 同样, text view 也能处理纯文本和带属性的文本。Text view 也实现了 UITextInputTraits 协议来控制键盘的行为和外观。

text view 除了处理多行文本的能力外,它最大的卖点就是你可使用、定制整个 Text Kit 堆栈。你能够为 layout managertext container 或 text storage 自定义行为或者替换为你自定义的子类。你能够看看 Max 的这篇 Text Kit 方面的文章

不幸的是,UITextView 在 iOS 7 中还有些问题,目前仍是 1.0 版本。它是基于 OS X Text Kit 从头开始从新实现的。在 iOS 7 以前,它是基于 Webkit 的,而且功能不多。咱们能够看看 Peter 和 Brent 关于这方面的文章。

Mac中又是什么状况呢?

如今咱们已经讨论过了 UIKit 中基本的 text 类,下面继续解释一下这些类在 AppKit 中结构的不一样之处。

首先,AppKit 中并无相似 UILabel 的控件。而显示文本最基本的类是 NSTextField。咱们将 text field 设为不可编辑、不可选择,这样便等同于 iOS 中的 UILabel 了。虽然 NSTextField 听起来相似于 UITextField,但 NSTextField 并不限制于单行文本。

NSTextView,换句话说,就是等同于 UITextView,它也为咱们揭露了整个 Cocoa Text System 堆栈。但它还包含了不少额外的功能。很大的缘由是由于 Mac 是一个具备指针设备(鼠标)的电脑。最值得注意的是包含了设置、编辑制表符的标尺。

Core Text

上面咱们讨论的全部类最终都使用 Core Text 布局、绘制真实的符号。Core Text 是一个很是强大的 framework ,它已经超出咱们这篇文章讨论的范围。可是若是你曾经须要经过彻底自定义的方式绘制文本(例如,贝塞尔曲线),那你须要详细的了解一下。

Core Text 在任何绘图方面都为你提供了充分的灵活性。然而,Core Text 很是难于操做。它是一个复杂的 Core Foundation / C API。Core Text 在排版方面给了你充分的访问权限。

在 Table View 中显示动态文本

可能和全部人都打过交道的字符串绘制就是最多见的可变高度的 table view cells。你能在社交媒体应用中见到这种。 table view 的 delegate 有一个方法:tableView:heightForRowAtIndexPath:,这即是用来计算高度的。iOS 7以前,很难经过一种可靠的方式使用它。

在咱们的示例中,咱们将会在 table view 中显示一列语录:

Table view with quotes

首先,为了实现彻底的自定义,咱们建立一个 UITableViewCell 的子类。在这个子类中,咱们须要亲自为咱们的 label 布局:

- (void)layoutSubviews
{
    [super layoutSubviews];
    self.textLabel.frame = CGRectInset(self.bounds, 
                                       MyTableViewCellInset,
                                       MyTableViewCellInset);
}

复制代码

MyTableViewCellInset 被定义为一个常量,因此咱们能够将它用在 table view 的 delegate 的高度计算中。最简单、准确计算高度的方法是将字符串转换成带属性的字符串,而后计算出带属性字符串的高度。咱们使用 table view 的宽度减去两倍的 MyTableViewCellInset 常量(前面和后面的空间)。为了计算真实的高度,咱们须要使用 boundingRectWithSize:options:context: 这个方法。

第一个参数是限制 text 大小的。咱们只须要关心宽度的限制,所以咱们为高度传一个最大值常量 CGFLOAT_MAX。第二个参数是很是重要的:若是你传一个其余值,bounding rect 无疑会出错。若是你想要调整字体缩放或进行追踪,你可使用第三个参数。最终,一旦咱们获得 boundingRect,咱们须要再次加上 inset:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CGFloat labelWidth = self.tableView.bounds.size.width - MyTableViewCellInset*2;
    NSAttributedString *text = [self attributedBodyTextAtIndexPath:indexPath];
    NSStringDrawingOptions options = NSStringDrawingUsesLineFragmentOrigin |
                                     NSStringDrawingUsesFontLeading;
    CGRect boundingRect = [text boundingRectWithSize:CGSizeMake(labelWidth, CGFLOAT_MAX)
                                             options:options
                                             context:nil];

    return (CGFloat) (ceil(boundingRect.size.height) + MyTableViewCellInset*2);    
}

复制代码

对于 bounding rect 的结果还有两件敏感的事情,除非你读了文档,否则这两件事你不必定会知道:返回值 size 是小数,文档中让咱们使用 ceil 将结果四舍五入。最终的结果多是会比实际的大一点。

请注意,由于咱们的 text 是纯文本,咱们建立的 attributedBodyTextAtIndexPath: 方法也会在 tableView:cellForRowAtIndexPath: 中用到。这样,咱们须要确保他们保持同步。

还有,经过阅读文档(以下截图),咱们发现 iOS 7 发布后,不少方法都被弃用了。若是你经过查找网页或 StackOverflow,你会发现不少测量字符高度的变通方法。由于苹果对文本框架进行了重大检修(在内部实现中,全部的东西都使用 TextKit 进行绘制了,而不是 WebKit),因此请使用新方法。

Deprecated string measuring methods

另外一个动态调整 table view cell 大小的选择就是使用 Auto Layout,你能够在这篇博文中找到更详细的说明。而后你能够利用 contained lables 的 intrinsicContentSize。然而,如今自动布局比手动计算要慢不少。但是对于原型开发,这很完美:它容许你快速调整 constraints 而且移动事物(特别当你 cell 中不止一个控件时这显得特别重要)。一旦你完成产品的设计迭代,而后你就能够用手动布局的方式从新编写代码。

使用 Text Kit 和 NSAttributedString 进行布局

使用 Text Kit,你将会拥有使人惊讶的灵活性来建立专业级别的文本布局。随着这些灵活性带来的是如何组合为数众多的选项来完成复杂的布局。

咱们准备给出几个示例并强调一些常见的布局问题,同时给出解决方案。

经典的文本

首先,让咱们看一些经典的文本。咱们将会使用 Jacomy-Régnier 的 Histoire des nombres et de la numération mécanique,并设为 Bodoni 字体。最终截屏效果以下所示:

Layout-Example-1

这些都是由 Text Kit 完成的。两段文字之间的装饰也是文本,使用的是 Bodoni Ornaments 字体。

咱们为文体风格使用调整好的 text。第一段从最左边开始,接下来的段落都会插入空格.

这有三种不一样的风格:文体风格,首行缩进的变化文体风格,装饰物风格。

让咱们先设置 body1stAttributes

CGFloat const fontSize = 15;

NSMutableDictionary *body1stAttributes = [NSMutableDictionary dictionary];
body1stAttributes[NSFontAttributeName] = [UIFont fontWithName:@"BodoniSvtyTwoITCTT-Book" 
                                                         size:fontSize];
NSMutableParagraphStyle *body1stParagraph = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
body1stParagraph.alignment = NSTextAlignmentJustified;
body1stParagraph.minimumLineHeight = fontSize + 3;
body1stParagraph.maximumLineHeight = body1stParagraph.minimumLineHeight;
body1stParagraph.hyphenationFactor = 0.97;
body1stAttributes[NSParagraphStyleAttributeName] = body1stParag
raph;

复制代码

将字体设置为 BodoniSvtyTwoITCTT。这是字体的 PostScript 名。若是想寻找字体名,咱们可使用 +[UIFont familyNames] 首先获得可用的字体系列集合。一个字体系列就是咱们所熟知的字型。每一个字型或字体系列有一个或多个字体。为了获得这些字体的名字,咱们可使用 +[UIFont fontNamesForFamilyName:]。注意一下,当你处理多样字体时,UIFontDescriptor 类很是有用,好比,当你想要知道一个给定的字体是什么版本的斜体。

许多设置位于 NSParagraphStyle。咱们建立一个默认风格的可变拷贝并作些调整。在咱们的例子中,咱们将会为字体大小加上 3 pt

接着,咱们会为这些段落的属性建立一个拷贝并修改他们来建立 boddyAttributes,(注意,这是咱们段落的属性,跟上文的 body1stParagraph 已经不是同一个了):

NSMutableDictionary *bodyAttributes = [body1stAttributes mutableCopy];
NSMutableParagraphStyle *bodyParagraph = 
  [bodyAttributes[NSParagraphStyleAttributeName] mutableCopy];
bodyParagraph.firstLineHeadIndent = fontSize;
bodyAttributes[NSParagraphStyleAttributeName] = bodyParagraph;

复制代码

咱们简单的建立了一个属性字典的可变拷贝,同时为了改变段落风格咱们也须要建立一个可变拷贝。将firstLineHeadIndent 设为和字体大小同样,咱们便会获得想要的空格缩进

接着,装饰段落风格:

NSMutableDictionary *ornamentAttributes = [NSMutableDictionary dictionary];
ornamentAttributes[NSFontAttributeName] = [UIFont fontWithName:@"BodoniOrnamentsITCTT"
                                                          size:36];
NSMutableParagraphStyle *ornamentParagraph = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
ornamentParagraph.alignment = NSTextAlignmentCenter;
ornamentParagraph.paragraphSpacingBefore = fontSize;
ornamentParagraph.paragraphSpacing = fontSize;
ornamentAttributes[NSParagraphStyleAttributeName] = ornamentParagraph;

复制代码

这个很容易理解。咱们使用装饰字体并将文本居中对齐。此外,在装饰字符的先后咱们都要加空白段落。

数据表格

接下来是显示数字的 table。咱们想要将分数的小数点对齐显示,即英语中的 “.”:

Layout-Example-2

为了达到这个目的,咱们须要指定 table 将中心停在分隔符上。

对于上面这个示例,咱们简单的作一下:

NSCharacterSet *decimalTerminator = [NSCharacterSet 
  characterSetWithCharactersInString:decimalFormatter.decimalSeparator];
NSTextTab *decimalTab = [[NSTextTab alloc] 
   initWithTextAlignment:NSTextAlignmentCenter
                location:100
                 options:@{NSTabColumnTerminatorsAttributeName:decimalTerminator}];
NSTextTab *percentTab = [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentRight
                                                        location:200
                                                         options:nil];
NSMutableParagraphStyle *tableParagraphStyle = 
  [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
tableParagraphStyle.tabStops = @[decimalTab, percentTab];

复制代码

列表

另外一个常见的使用状况就像 list 这样:

Layout-Example-3

(图片来自 Robert's Rules of Order,做者为 Henry M. Robert)

缩进相对容易设置。咱们须要确保序列号 “(1)” 和 text 或者着重号和 text 之间有一个制表符。而后咱们像这样调整段落的风格:

NSMutableDictionary *listAttributes = [bodyAttributes mutableCopy];
NSMutableParagraphStyle *listParagraph = 
  [listAttributes[NSParagraphStyleAttributeName] mutableCopy];
listParagraph.headIndent = fontSize * 3;
listParagraph.firstLineHeadIndent = fontSize;
NSTextTab *listTab = [[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentNatural
                                                     location:fontSize * 3 
                                                      options:nil];
listParagraph.tabStops = @[listTab];
listAttributes[NSParagraphStyleAttributeName] = listParagraph;

复制代码

咱们将 headIndent 设置为真实文本的缩进,将 firstLineHeadIndent 设置为咱们但愿着重号具备的缩进。最终,和 headIndent 同样,咱们须要在相同的位置增长一个制表符。着重号后的制表符会确保这行文本从正确的位置开始绘制。


小编这里推荐一个群:691040931 里面有大量的书籍和面试资料,不少的iOS开发者都在里面交流技术

资料截图.png

原文 String Rendering

相关文章
相关标签/搜索