iOS 和 Mac OS X 的字符串渲染

简介: 为了简单起见,我们先看看UIKit在字符串渲染方面为我们提供了哪些控件。之后我们将讨论一下对于字符串的渲染, iOS 和 OS X 系统中有哪些相似和不同。UIKit 提供了很多可以在屏幕上显示和编辑文本的类。每一个类都是为特定使用情况准备的,所以为了避免不必要的问题,为你手上的任务挑选正确的工具是非常重要的。

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

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


UIKit 提供了很多可以在屏幕上显示和编辑文本的类。每一个类都是为特定使用情况准备的,所以为了避免不必要的问题,为你手上的任务挑选正确的工具是非常重要的。


UILabel

UILabel是将文本绘制到屏幕上最简单的方式。它是UIView的一个子类,用来显示少量的只读文本。文本可以被展示在一行或多行,如果文本不能适应指定的空间我们还可以使用不同的方式裁剪。尽管labels使用的方式很简单,但是这里有几个技巧还是值得我们提一提的。


labels默认只显示一行,但是你可以将numberOfLines属性设为其他值来改变这一行为。将它设置为一个大于1的值,文本的行数将会被限制为这个指定的值,如果设置为0则是告诉label不管文本占多少行都显示出来。


通过设置text属性,Labels可以显示简单的纯文本,而设置attributedText属性则可以让label显示富文本。当使用纯文本的时候,你可以使用label的font,textColor, textAlignment,shadowColor和shadowOffset属性改变它的外观,如果你希望改变整个程序所有Label的风格,你也可以使用[UILabel appearance] 这个方法来进行全局的更改。


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


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


如果你使用这些选项让UIKit压缩你的文本以适配,如果压缩的时候想让文本保持在同一条基线上或需要对齐到左上角,那么你可以定义baselineAdjustment属性。然而,这个选项只对单行labels起作用。


当你使用上述的方法让文本自动缩放大小以适配你的 UILabel 时,你可以使用 baselineAdjustment 这个属性来调整缩放时文本的基准线,是保持统一基准线还是对齐到你的 Label 的左上角。注意,这个属性仅在单行的 Lable (即 numberOfLines 属性值为1时)中生效。


UITextField

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


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


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


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


UITextView

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


但除了text view处理多行文本的能力外,它最大的卖点就是你可以使用、定制整个Text Kit堆。你可以自定义行为或为layout manager、text container或text storage替换你自定义的子类。objc.io issue #5中有提到Text Kit方面的文章


不幸的是,UITextView在iOS7中还有些问题。目前还是1.0版本。它是基于OS X Text Kit从头开始重新实现的。iOS7之前,它是基于Webkit并且功能很少。


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,它已经超出我们这篇文章讨论的范围。但是如果你曾经需要通过完全自定义的方式绘制文本(e.g.贝塞尔曲线),那你需要详细的了解一下。


Core Text在任何绘图方面为你提供了充分的灵活性。然而,Core Text非常难于操作。它是一个复杂的Core Foundation / C API。Core Text 在排版方面给了你充分的使用权。


在Table View中显示动态Text

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


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

image.png

首先,为了实现完全的自定义,我们创建一个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无疑会出错。如果你想要调整字体缩放and/or追踪,你可以使用第三个参数。最终,一旦我们得到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 = 1;

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

}

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

请注意,因为我们的text是纯文本时,我们创建的attributedBodyTextAtIndexPath:方法也会在tableView:cellForRowAtIndexPath:中用到。这样,我们需要确保他们保持同步。


还有,看看文档(如下截图),我们可以看到iOS7发布后,很多方法都被弃用了。如果你通过查找网页或StackOverflow,你会发现很多答案、以及测量字符大小的变通方法。因为text system受到了重大检修(在内部实现中,所有的东西都使用TextKit进行绘制了,而不是WebKit),所以请使用新方法。

image.png

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


使用Text Kit和NSAttributedString布局

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

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


经典的文本

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

image.png

这些都是由Text Kit完成的。两段文字之间的装饰也是text,使用的是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] = body1stParagraph;

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

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

接着,我们会为这些段落的属性创建一个拷贝并修改他们来创建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。我们想要将分数的小数点对齐显示,i.e.英语中的”.”:

image.png

为了达到这个目的,我们需要指定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这样:

image.png

缩进相对容易设置。我们需要确保序列号(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一样,我们需要在相同的位置增加一个制表符。着重号后的制表符会确保这行文本从正确的位置开始绘制。

相关文章
|
25天前
|
Android开发 Swift iOS开发
iOS和安卓作为主流操作系统,开发者需了解两者差异以提高效率并确保优质用户体验。
【10月更文挑战第1天】随着移动互联网的发展,智能手机成为生活必需品,iOS和安卓作为主流操作系统,各有庞大的用户群。开发者需了解两者差异以提高效率并确保优质用户体验。iOS使用Swift或Objective-C开发,强调简洁直观的设计;安卓则采用Java或Kotlin,注重层次与动画。Swift和Kotlin均有现代编程特性。此外,iOS设备更易优化,而安卓需考虑更多兼容性问题。iOS应用仅能通过App Store发布,审核严格;安卓除Google Play外还可通过第三方市场发布,审核较宽松。开发者应根据需求选择合适平台,提供最佳应用体验。
49 3
|
26天前
|
Java Linux Android开发
移动应用开发与操作系统的交互:深入理解Android和iOS
在数字时代,移动应用成为我们日常生活的一部分。本文将深入探讨移动应用开发的核心概念、移动操作系统的工作原理以及它们如何相互作用。我们将通过实际代码示例,展示如何在Android和iOS平台上创建一个简单的“Hello World”应用,并解释其背后的技术原理。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和知识。
|
2天前
|
搜索推荐 Android开发 iOS开发
安卓与iOS的较量:选择最适合你的移动操作系统
在智能手机市场上,安卓和iOS一直是两大主流操作系统。本文将深入探讨这两个系统的优缺点,帮助你根据自己的需求做出最佳选择。
|
20天前
|
Python
【10月更文挑战第6天】「Mac上学Python 11」基础篇5 - 字符串类型详解
本篇将详细介绍Python中的字符串类型及其常见操作,包括字符串的定义、转义字符的使用、字符串的连接与格式化、字符串的重复和切片、不可变性、编码与解码以及常用内置方法等。通过本篇学习,用户将掌握字符串的操作技巧,并能灵活处理文本数据。
48 1
【10月更文挑战第6天】「Mac上学Python 11」基础篇5 - 字符串类型详解
|
3天前
|
搜索推荐 Android开发 iOS开发
安卓vs. iOS:操作系统的终极对决####
本文探讨了安卓和iOS两种主流移动操作系统的特点,从用户体验、系统生态、硬件兼容性等维度进行对比分析。通过深入浅出的方式,揭示了两者在技术层面的优劣及未来发展趋势,旨在帮助用户更好地理解并选择适合自己的平台。 ####
|
4天前
|
安全 搜索推荐 Android开发
Android vs. iOS:解锁智能手机操作系统的奥秘####
【10月更文挑战第21天】 在当今这个数字化时代,智能手机已成为我们生活中不可或缺的伙伴。本文旨在深入浅出地探讨两大主流操作系统——Android与iOS的核心差异、优势及未来趋势,帮助读者更好地理解这两个平台背后的技术哲学和用户体验设计。通过对比分析,揭示它们如何塑造了我们的数字生活方式,并展望未来可能的发展路径。无论您是技术爱好者还是普通用户,这篇文章都将带您走进一个充满创新与可能性的移动世界。 ####
15 3
|
6天前
|
安全 Android开发 iOS开发
Android vs iOS:探索移动操作系统的设计与功能差异###
【10月更文挑战第20天】 本文深入分析了Android和iOS两个主流移动操作系统在设计哲学、用户体验、技术架构等方面的显著差异。通过对比,揭示了这两种系统各自的独特优势与局限性,并探讨了它们如何塑造了我们的数字生活方式。无论你是开发者还是普通用户,理解这些差异都有助于更好地选择和使用你的移动设备。 ###
15 3
|
9天前
|
搜索推荐 Android开发 数据安全/隐私保护
安卓vs. iOS:两大操作系统的终极对决####
【10月更文挑战第17天】 本文将深入浅出地探讨安卓和iOS这两大智能手机操作系统的差异与优劣,通过对比它们的技术架构、用户体验、市场表现及未来发展趋势,为读者提供一个全面而客观的视角。无论你是技术爱好者还是普通消费者,都能从中获得有价值的信息。 ####
26 2
|
8天前
|
安全 Android开发 数据安全/隐私保护
Android vs. iOS:移动操作系统的巅峰对决###
【10月更文挑战第18天】 本文深入探讨了Android与iOS两大移动操作系统的核心差异、优势与不足,从用户体验、应用生态、系统更新与安全性等多个维度进行了全面对比。我们旨在揭示两大平台背后的设计理念,帮助用户根据自身需求做出更明智的选择。Android以其开放性和高度可定制性著称,为开发者和用户提供了广阔的创新空间;而iOS则凭借其封闭的生态系统和严格的质量控制,提供了流畅且一致的用户体验。两大系统各有千秋,竞争与共生中推动着移动通信技术的不断进步。 ###
|
2月前
|
监控 Android开发 iOS开发
深入探索安卓与iOS的系统架构差异:理解两大移动平台的技术根基在移动技术日新月异的今天,安卓和iOS作为市场上最为流行的两个操作系统,各自拥有独特的技术特性和庞大的用户基础。本文将深入探讨这两个平台的系统架构差异,揭示它们如何支撑起各自的生态系统,并影响着全球数亿用户的使用体验。
本文通过对比分析安卓和iOS的系统架构,揭示了这两个平台在设计理念、安全性、用户体验和技术生态上的根本区别。不同于常规的技术综述,本文以深入浅出的方式,带领读者理解这些差异是如何影响应用开发、用户选择和市场趋势的。通过梳理历史脉络和未来展望,本文旨在为开发者、用户以及行业分析师提供有价值的见解,帮助大家更好地把握移动技术发展的脉络。
67 6