Apple - Cocoa Text Architecture Guide

翻译整理自:Cocoa Text Architecture Guide
https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Introduction/Introduction.html#//apple_ref/doc/uid/TP40009459


文章目录

  • 一、关于 Cocoa 文本系统
    • 1、概览
      • 大多数应用程序都可以使用 Cocoa 文本系统
      • 印刷概念对于理解文本系统至关重要
      • 文本系统由视图、控制器和存储类组成
      • 属性表征文本和文档
      • 字体对象、字体面板和字体管理器提供字体处理
      • 文本对象是文本编辑的关键
    • 先决条件
    • 也可以看看
  • 二、OS X 中的文本处理技术
  • 三、印刷概念
    • 1、字符和字形
    • 2、字体和字型
    • 3、文本布局
  • 四、文本系统组织
    • 1、Cocoa 文本系统的功能区域
    • 2、Cocoa 文本系统的类层次结构
    • 3、MVC 和文本系统
    • 4、创建文本系统对象
      • 文本视图创建对象
      • 您的应用明确创建对象
    • 5、常见配置
  • 五、文本字段、文本视图和字段编辑器
    • 1、文本字段
    • 2、文本视图
    • 3、现场编辑
  • 六、文本属性
    • 1、角色属性
      • 存储角色属性
      • 属性修复
    • 2、临时属性
    • 3、段落属性
    • 4、字形属性
    • 5、文档属性
  • 七、字体处理
    • 1、字体面板
      • 创建字体面板
      • 使用字体面板
    • 2、使用字体对象
      • 字体描述符
      • 查询字体规格
      • 查询标准字体变体
      • 字符、字形和布局管理器
      • 获取字形的视图坐标
    • 3、使用字体管理器
      • 创建字体管理器
      • 处理字体变化
      • 手动转换字体
      • 设置字体样式和特征
      • 检查字体
      • 自定义字体转换系统
  • 八、文本编辑
    • 1、编辑环境
    • 2、按键输入消息序列
    • 3、拦截关键事件
    • 4、文本视图代理
    • 5、文本视图代理消息和通知
      • 程序化设置焦点和选择
    • 6、子类NSTextView
      • 正在更新状态
      • 自定义导入类型
      • 更改选择行为
      • 准备更改文本
      • 文本更改通知和代理消息
      • 智能插入和删除
    • 7、创建自定义文本视图
      • 实现文本输入支持
      • 管理标记的文本
      • 与文本输入语境的沟通
    • 8、使用字段编辑器
      • 字段编辑器的工作原理
      • 在字段编辑器中使用委派和通知
        • 更改默认行为
        • 在NSTextField对象中获取换行符
      • 使用自定义字段编辑器
        • 为什么要使用自定义字段编辑器?
        • 如何替换自定义字段编辑器
      • 字段编辑器相关方法


一、关于 Cocoa 文本系统

Cocoa 文本系统是 OS X 中的主要文本处理系统,负责处理和显示 Cocoa 中所有可见文本。
它通过与文本相关的 AppKit 类提供一整套高质量的排版服务,使应用程序能够创建、编辑、显示和存储具有精细排版所有特征的文本,例如字距调整、连字、换行和对齐。

在这里插入图片描述


1、概览

Cocoa 文本系统为大多数应用程序提供文本编辑和布局功能。
该系统的面向对象设计提供了灵活性和易用性。


大多数应用程序都可以使用 Cocoa 文本系统

如果您的应用程序需要显示文本,尤其是当其用户需要输入和编辑文本时,则应使用 Cocoa 文本系统。
Cocoa 文本系统是 OS X 中的两个文本处理系统之一。
另一个是 Core Text,它为 AppKit 等更高级的引擎提供低级、基本的文本布局和字体处理功能。

相关章节: OS X 中的文本处理技术


印刷概念对于理解文本系统至关重要

Cocoa 文本系统将字符编码为 Unicode 值。
它将字符转换为字形,包括连字和其他上下文形式,并处理字体、样式、字体和系列。
该系统进行文本布局,在水平或垂直方向上放置字形,使用字体度量信息,并在适当时使用字距调整。
它执行高质量的换行和连字,以创建具有正确对齐或两端对齐的文本行。

相关章节: 印刷概念


文本系统由视图、控制器和存储类组成

Cocoa 文本系统被抽象为一组类,这些类代表模块化、分层的功能区域,反映了模型-视图-控制器设计范式。
系统的顶层是各种视图的用户界面层,底层存储数据模型,中间层由解释键盘输入并排列文本以供显示的控制器组成。

四个主要文本系统类NSTextView —— NSLayoutManagerNSTextContainerNSTextStorage —— 可以通过多种方式配置,以实现不同的文本处理目标。

相关章节: 文本系统组织、文本字段、文本视图和字段编辑器


属性表征文本和文档

Cocoa 文本系统处理五种属性:字符属性,例如字体和大小;处理或显示期间使用的临时属性,例如拼写错误的单词的下划线;段落属性,例如对齐方式和制表位;可以控制特定字形的特殊处理的字形属性;以及文档属性,例如边距和纸张大小。

相关章节: 文本属性


字体对象、字体面板和字体管理器提供字体处理

字体面板(也称为字体窗口)是一个用户界面对象,它显示可用字体系列和样式的列表,让用户可以预览它们并更改用于显示文本的字体。
文本视图与NSFontPanelNSFontManager对象配合使用 以实现字体处理系统。
您可以使用NSFont类创建字体对象并查询它们以获取字体规格和详细的字形布局信息。

相关章节: 字体处理


文本对象是文本编辑的关键

通常,文本编辑是通过用户直接使用文本视图进行操作来完成的,但也可以通过与文本存储对象的编程交互来完成。
文本输入系统将键盘事件转换为命令和文本输入。
您可以使用文本系统对象的多种方法、通过强大的 Cocoa 通知和代理机制来自定义编辑行为,或者在极端情况下,通过使用自定义子类替换文本视图本身来自定义编辑行为。

相关章节: 文本编辑


先决条件

要理解本文档中的信息,您应该理解文本系统用户界面层编程指南 中的材料。
此外,您还应该对 Cocoa 编程范例有一般了解,并且要理解代码示例,您还应该熟悉 Objective-C 语言。


也可以看看

以下文档描述了 Cocoa 文本系统的其他方面:

  • *文本系统用户界面层编程指南*描述了 Cocoa 文本系统的高级接口,对于大多数应用程序来说已经足够了。
  • *文本系统存储层概述*讨论了 Cocoa 文本系统用于存储文本的低级设施。
  • *文本布局编程指南*描述了 Cocoa 文本系统如何在页面上布局文本,以适合显示和打印。

下面的示例代码项目说明了如何使用 Cocoa 文本系统的许多 API:

  • CircleViewNSView是一个小应用程序,带有一个在圆圈内绘制文本的演示子类
  • *NSFontAttributeExplorer*演示了如何使用 收集和显示已安装字体的各种度量信息NSFont
  • *TextInputView*演示了视图如何实现NSTextInputClient协议。
  • *TextViewDelegate*演示如何使用文本视图的代理来控制选择和用户输入。


二、OS X 中的文本处理技术

Macintosh 操作系统从一开始就提供了复杂的文本处理和排版功能。
事实上,这些功能引发了桌面出版革命。
多年来,该平台的文本处理功能不断发展,变得更加先进、高效和易于使用。
OS X 提供了现代文本处理功能,这些功能可通过 Cocoa 文本系统的类以及 Core Text 的不透明类型和函数供所有应用程序使用。

任何应用程序的文本处理组件都是软件设计人员面临的最大挑战之一。
即使最基本的文本处理系统也必须相对复杂,允许文本输入、布局、显示、编辑、复制和粘贴以及许多其他功能。
但开发人员和用户的期望甚至比这些基本功能更多,他们期望即使是简单的编辑器也能支持多种字体、各种段落样式、嵌入图像、拼写检查和其他功能。

Cocoa 文本系统提供了所有这些基本和高级文本处理功能,并且还满足了日益互联的计算世界的额外要求:支持世界上所有现存语言的字符集、强大的布局功能以处理各种文本方向性和非矩形文本容器,以及复杂的排版功能(例如控制字距和连字)。
Cocoa 的面向对象文本系统旨在提供所有这些功能,而您无需了解或与系统进行超出满足应用程序需求所需的更多交互。

Cocoa 文本系统的基础是 Core Text,它为 AppKit、WebKit 等高级引擎提供低级、基本的文本布局和字体处理功能。
Core Text 为许多 Cocoa 文本技术提供实现。
应用程序开发人员通常不需要直接使用 Core Text。
但是,必须直接使用它的开发人员可以使用 Core Text API,例如那些使用自己的布局引擎编写应用程序的开发人员以及那些将基于 ATSUI 或 QuickDraw 的代码库移植到现代世界的开发人员。

要确定哪种 OS X 文本技术适合您的应用程序,请遵循以下准则:

  • 如果可能,请使用 Cocoa 文本。
    NSTextView类 是 OS X 中最先进的全功能、灵活的文本视图。
    对于少量文本,请使用NSTextField
    有关文本视图的更多信息,请参阅 文本系统用户界面层编程指南
  • 要在您的应用程序中显示 Web 内容,请使用 WebKit。
    有关 WebKit 的更多信息,请参阅 WebKit Objective-C 编程指南
  • 如果您有自己的页面布局引擎,则可以使用 Core Text 生成字形并将它们相互定位。
    有关 Core Text 的更多信息,请参阅 Core Text 编程指南

三、印刷概念

本章定义了一些与文本系统相关的重要排版概念。
如果您已经熟悉排版,则可以跳过本章。


1、字符和字形

一个人物 是书面语言中承载意义的最小单位。
字符可以对应于语言口语中的特定声音,罗马字母也是如此;它们可以表示整个单词,例如汉字;或者它们可以表示独立的概念,例如数学符号。
然而,在每种情况下,字符都是一个抽象概念。

虽然字符必须以可识别的形状在显示区域中表示,但它们并不与该形状完全相同。
也就是说,字符可以以各种形式绘制,但仍然是同一个字符。
例如,“大写字母 A”字符可以用不同的大小或不同的笔画粗细绘制,它可以倾斜或垂直,并且可以具有某些可选的形式变化,例如衬线。
字符的这些各种具体形式中的任何一种都称为字形
图 2-1显示了代表字符“大写字母 A”的不同字形。


图 2-1 字符 A 的字形

在这里插入图片描述


字符和字形并不具有一一对应关系。
在某些情况下,一个字符可能由多个字形表示,例如“é”可能由“e”字形与重音符号“´”组合而成。
在其他情况下,单个字形可能代表多个字符,例如连字,即连在一起的字母。
图 2-2显示了单个字符以及它们相邻时经常使用的单字形连字。


图 2-2 连字

在这里插入图片描述


连字是上下文形式的一个例子,其中用于表示字符的字形会根据其旁边的字符而变化。
其他上下文形式包括用于表示单词开头或结尾的字符的替代字形。

计算机将字符存储为通过编码表映射到其对应字符的数字。
OS X 原生的编码方案称为Unicode
Unicode 标准为世界上每种现代书面语言中的每个字符提供了一个唯一的编号,与所使用的平台、程序和编程语言无关。
这一通用标准解决了不同计算机系统使用数百种相互冲突的编码方案这一长期存在的问题。
它还具有简化处理双向文本和上下文形式的功能。

字形也由数字代码表示,称为字形代码。
用于描述字符的字形由 Cocoa 布局管理器在组合和布局处理期间选择。
布局管理器确定使用哪些字形以及将它们放置在显示屏或视图中的何处。
布局管理器缓存正在使用的字形代码,并提供在字符和字形之间以及字符和视图坐标之间进行转换的方法。
(有关布局过程的更多信息,请参阅 文本布局。)


2、字体和字型

字体书面语言中部分或全部字符的一组视觉相关形状。
例如,Times 是一种字体,由斯坦利·莫里森于 1931 年为伦敦的*《泰晤士报*》设计。
《泰晤士报》中的所有字母形式在外观上都是相关的,在字干(垂直笔画)和字重(字母主体中的圆形)和其他元素之间具有一致的比例。
当以文本块的形式布局时,字体中的形状可以协同工作以提高可读性。

字体样式*(*或简称为样式)是字体的显著视觉特征。
例如,罗马字体字体样式的特点是直立字母具有衬线和比水平线更粗的字干。
在斜体字体样式中,字母向右倾斜且呈圆形,类似于草书或手写字母的形状。
一种字体通常有几种相关的字体样式。

字体是一系列以一致大小、字体和字体样式描绘字符的字形*。
*字体旨在用于特定的显示环境。
字体包含所有上下文形式的字形,例如连字,以及普通字符形式。

字体系列是一组共享字体但字体样式不同的字体。
例如,Times 是字体系列(以及字体名称)。
Times Roman 和 Times Italic 是 Times 系列中两种字体的名称。
图 2-3显示了 Times 字体系列中的几种字体。


图 2-3 Times 字体系列中的字体
在这里插入图片描述


Cocoa 中提供的样式(也称为特征)包括粗体、斜体、压缩、扩展、窄体、小型大写字母、海报字体和固定间距等变体。
Cocoa 文本系统中的字体描述符提供了字体匹配功能,因此您可以通过创建仅包含字体系列名称或粗细等的字体描述符来部分描述字体,然后可以找到系统上与给定特征匹配的所有字体。


3、文本布局

文本布局**是在显示设备上一个称为文本视图的区域中排列字形的过程,该区域代表与传统排版中的页面类似的区域。
字形相对于彼此的排列顺序称为文本方向
在英语和其他源自拉丁语的语言中,字形并排放置以形成由空格分隔的单词。
单词按行排列,从文本视图的左上角开始,从左到右进行,直到文本到达视图的右侧。
然后,文本在视图左侧上一行的开头下方开始新行,布局以相同的方式进行到文本视图的底部。

在其他语言中,字形布局可能大不相同。
例如,某些语言从右到左或垂直而不是水平排列字形。
在同一行中混合使用不同文本方向的语言(例如英语和希伯来语)是很常见的,尤其是在技术写作中。
某些书写系统甚至每隔一行交替布局方向(这种排列称为牛耕式书写)。
某些语言不会将字形分组为用空格分隔的单词。
此外,某些应用程序要求任意排列字形;例如,在图形设计环境中,布局可能要求字形以非线性路径排列。

要从字形字符串创建行,布局引擎必须通过找到结束一行并开始下一行的点来执行换行
在 Cocoa 文本系统中,您可以在单词或字形边界处指定换行。
在罗马文本中,在字形之间断开的单词需要在断点处插入连字符字形。

Cocoa 布局管理器沿着一条不可见的线(称为基线)布置字形。
在罗马文本中,基线是水平的,大多数字形的底边都位于其上。
有些字形延伸到基线以下,包括像“g”这样的字符,它们有
下行字符
,或“尾巴”,以及像“O”这样的大圆字符,它们必须略微延伸到基线以下以补偿视觉效果。
其他书写系统将字形置于基线以下或居中。
每个字形都包括一个布局管理器使用原点将其与基线正确对齐。

字形设计师为字体提供了一组测量值,称为度量,描述字体中每个字形周围的间距。
布局管理器使用这些度量来确定字形的位置。
在水平文本中,字形具有称为前进宽度,测量沿基线到下一个字形原点的距离。
通常在原点和字形左侧之间会有一些空间,这被称为左侧轴承
字形右侧和前进宽度描述的点之间也可能有空间,这称为右侧轴承
字形的垂直尺寸由两个度量提供,称为上升下降
上升是从原点(在基线上)到字体中最高字形顶部的距离。
下降是从基线以下到字体最深下降线底部的距离。
包围字形可见部分的矩形称为边界矩形或边界框。
图 2-4说明了这些指标。


图 2-4 字形规格
在这里插入图片描述


默认情况下,在水平文本中,排版员使用前进宽度并排放置字形,从而产生标准的字形间距。
但是,在某些组合中,文本可以通过以下方式变得更易读:字距调整,即缩小或拉伸两个字形之间的间距。
一个非常常见的字距调整示例是大写字母 W 和大写字母 A 之间的字距调整,如图2-5所示。
字体设计师在字体的度量中包含了字距调整信息。
Cocoa 文本系统提供了关闭字距调整、使用字体提供的默认设置或在整个文本选择中收紧或放松字距调整的方法。


图 2-5 字距 调整
在这里插入图片描述


字体系统通常使用称为,在 OS X 中精确测量为每英寸 72 点。
将字体的上升距离和下降距离相加可得出字体的点大小

排版时行与行之间所加的空格称为行距,取自传统金属活字排版中用于此目的的铅块。
(行距有时也称为行距。)上升量 加上下降量 加上行距 之和构成了字体的行高

虽然上述字体设计概念可能有些深奥,但大多数在计算机或打字机上创建文档的人都熟悉页面上的文本布局元素。
例如,边距是页面边缘与文本区域之间的空白区域,布局引擎会在此放置字形。
对齐描述了文本行相对于边距的放置方式。
例如,水平文本可以右对齐、左对齐或居中对齐,如图2-6所示。


图 2-6 文本相对于边距的对齐方式

在这里插入图片描述


文本行也可以对齐;对于水平文本,行通过改变单词间和字形间的间距在左右边缘对齐,如图2-7所示。
在将文本流拆分成行并添加连字符并进行其他字形替换后,系统会根据需要执行对齐和对齐。


图 2-7 对齐文本

在这里插入图片描述


四、文本系统组织

Cocoa 文本系统被抽象为一组类,这些类相互作用以提供系统的所有文本处理功能。
这些类代表具有明确定义的接口的特定功能区域,使应用程序能够修改系统的行为,甚至用自定义子类替换部分。
Cocoa 文本系统的设计使您无需了解或与系统进行更多交互,只要满足应用程序的需求即可。

对于大多数开发人员来说,NSTextView类就是您需要了解的全部内容。
NSTextView为文本系统提供用户界面。
有关NSTextView的详细信息,请参阅 NSTextView 类参考

如果你需要更灵活、更程序化的访问文本,你需要了解存储层和NSTextStorage类。
当然,要访问所有可用功能,您可以与支持文本系统的任何类进行交互。


1、Cocoa 文本系统的功能区域

图 3-1显示了文本系统的主要功能区域,其中顶部是用户界面层,底部是存储层,中间区域是布局文本以供显示的组件。
这些层分别代表视图、控制器和模型概念,如 MVC 和文本系统中所述。


图3-1 Cocoa文本系统的主要功能区域

在这里插入图片描述


文本类在界面的丰富性和复杂性方面超过了 AppKit 中的大多数其他类。
它们的设计目标之一是提供一套全面的文本处理功能,这样您几乎不需要创建子类。
除其他外,文本对象例如NSTextView可以:

  • 控制用户是否可以选择或编辑文本。
  • 通过使用“字体”菜单和“字体”面板(也称为*“字体”窗口*)来控制文本的字体和布局特征。
  • 让用户通过操作标尺来控制段落的格式。
  • 控制其文本和背景的颜色。
  • 根据单词或字符换行。
  • 在文本中显示图形图像。
  • 以 RTFD(包含 TIFF 或 EPS 图像或附加文件的富文本格式文件)的形式向文件写入文本或从中读取文本。
  • 让另一个对象(代理)动态控制其属性。
  • 允许用户在应用程序内和应用程序之间复制和粘贴文本。
  • 让用户在NSTextView对象之间复制和粘贴字体和格式信息。
  • 让用户检查文本中单词的拼写。

图形用户界面构建工具(如 Interface Builder)可以让你以几种不同的配置访问文本对象,比如NSTextFieldNSFormNSScrollView对象。
这些类配置文本对象以用于其特定用途。
此外,同一窗口内的所有NSTextFieldNSFormNSButton对象(简而言之,所有通过关联单元格访问文本对象的对象)共享同一文本对象,称为字段编辑器
因此,通常最好在满足您的需求时使用这些类之一,而不是自己创建文本对象。
但是,如果这些类之一无法为您的目的提供足够的灵活性,您可以通过编程方式创建文本对象。

文本对象通常与其他各种对象密切协作。
其中一些对象(例如代理或嵌入式图形对象)需要您进行一些编程。
其他对象(例如字体面板、拼写检查器或标尺)除了决定是否应启用或禁用服务外,无需做任何工作。

要控制屏幕或打印页面上的文本布局,可以使用将NSTextStorage存储库 链接到NSTextView显示其内容的对象。
这些对象属于NSLayoutManagerNSTextContainer课程。

NSTextContainer 对象定义可以布局文本的区域。
通常,文本容器定义矩形区域,但通过创建NSTextContainer子类,您可以创建其他形状:例如圆形、五边形或不规则形状。
NSTextContainer不是用户界面对象,因此它不能显示任何内容或接收来自键盘或鼠标的事件。
它只是描述可以填充文本的区域,并且不依赖于任何特定的坐标系。
NSTextContainer对象也不存储文本 - 这是NSTextStorage对象的工作。

NSLayoutManager类的布局管理器对象 负责协调其他文本处理对象的操作。
它负责将NSTextStorage对象中的数据转换为NSTextView对象显示中的渲染文本。
它还负责监督NSTextContainer对象定义区域内的文本布局。


2、Cocoa 文本系统的类层次结构

除了文本系统中的四个主要类NSTextStorageNSLayoutManagerNSTextContainerNSTextView 之外,还有许多辅助类和协议。
图 3-2 提供了文本系统的更完整图景。
尖括号内的名称,例如<NSCopying>协议。


图 3-2 Cocoa 文本系统类层次结构

在这里插入图片描述


3、MVC 和文本系统

Cocoa 文本系统的架构既模块化又分层,以提高其易用性和灵活性。
其模块化设计反映了模型视图控制器范式(起源于 Smalltalk-80),其中数据、其可视化表示和链接两者的逻辑由单独的对象表示。
在文本系统中,NSTextStorage持有模型的文本数据,NSTextContainer对布局区域的几何形状进行建模,NSTextView提出观点,NSLayoutManager作为控制者进行干预,以确保数据和其在屏幕上的表示保持一致。

这种职责分解使得每个组件对其他组件实现的依赖性降低,并且使得用改进版本替换单个组件变得更加容易,而无需重新设计整个系统。
为了说明文本处理组件的独立性,请考虑使用文本系统的不同子集可能进行的一些操作:

  • 仅使用一个NSTextStorage对象,您就可以在文本中搜索特定的字符、字符串、段落样式等等。
  • 仅使用一个NSTextStorage对象,您就可以以编程方式操作文本,而无需承担布局显示的开销。
  • 使用文本系统除NSTextView对象之外的所有组件,您可以计算布局信息、确定换行符的位置、计算总页数等等。

文本系统的分层减少了完成常见文本处理任务所需学习的内容。
事实上,许多应用程序仅通过NSTextView类的 API 与系统交互。


4、创建文本系统对象

有两种标准方法可以创建文本系统的四个主要类的对象网络来处理文本编辑、布局和显示:在一种情况下,文本视图创建并拥有其他对象;在另一种情况下,您明确创建所有对象,并且文本存储拥有它们。


文本视图创建对象

您创建并维护对一个NSTextView对象的引用,该对象会自动创建、互连和拥有其他文本系统对象。
大多数 Cocoa 应用程序都使用此技术,并通过 NSTextView 与文本系统进行高级别的交互。
您可以使用 Xcode 的图形界面编辑器 Interface Builder 创建文本视图并让它创建其他文本对象,或者您也可以通过编程方式执行相同的操作。

要在 Interface Builder 中创建文本视图对象,请将文本视图从对象库拖到应用窗口上。
当您的应用启动并加载其 nib 文件时,它会实例化一个NSTextView对象并将其嵌入滚动视图中。
在后台,文本视图对象会自动实例化和管理NSTextContainerNSLayoutManagerNSTextStorage对象。

要以编程方式创建文本视图对象并让其创建和拥有其他对象,请使用 initWithFrame: 初始化方法 NSTextView

文本视图所有权技术是设置文本系统对象 web 的最简单、最干净的方法。
但是,它会创建不支持分页或复杂布局的单一文本流,如 常见配置中所述。
对于其他配置,您必须明确创建对象。


您的应用明确创建对象

您显式创建所有四个文本对象并将它们连接在一起,仅维护对NSTextStorage对象的引用。
然后文本存储对象拥有并管理网络中的其他文本对象。

要显式创建文本系统对象并将它们连接在一起,请使用本节中显示的步骤。
例如,此代码可以驻留在应用程序代理的通知方法applicationDidFinishLaunching:的实现中。
它假设textStorage是代理对象的实例变量。
它还假设windowwindowView是应用程序代理的属性,表示应用程序主窗口及其内容视图的出口。

  1. 使用allocinit…消息 以正常方式创建NSTextStorage对象。
    当你显式创建文本系统时,你只需要保留对 NSTextStorage对象的引用。
    系统的其他对象归文本存储对象所有,并由系统自动释放。
textStorage = [[NSTextStorage alloc]
              initWithString:@"Here's to the ones who see things different."];

  1. 创建一个NSLayoutManager对象 并将其连接到文本存储对象。
    布局管理器需要一些支持对象(例如那些帮助其生成字形或在文本容器内定位文本的对象)才能运行。
    它在初始化时会自动创建这些对象(或连接到现有对象)。
NSLayoutManager *layoutManager;
layoutManager = [[NSLayoutManager alloc] init];
[textStorage addLayoutManager:layoutManager];

  1. 创建一个NSTextContainer对象,用一个大小初始化它,并将其连接到布局管理器。
    文本容器的大小就是 它所显示视图的大小 —— 在本例中 self.windowView 是应用主窗口的内容视图。
    创建文本容器后,将其添加到布局管理器拥有的容器列表中。
    如果您的应用有多个文本容器,您可以在此步骤中创建并添加它们,也可以根据需要延迟创建它们。
NSTextContainer *textContainer;
textContainer = [[NSTextContainer alloc]
                initWithContainerSize:self.windowView.frame.size];
[layoutManager addTextContainer:textContainer];

  1. 创建一个NSTextView对象,用框架初始化它,并将其连接到文本容器。
    当您显式创建文本系统的对象 web 时,必须使用 initWithFrame:textContainer: 方法 来初始化文本视图。
    此初始化方法只初始化接收器并设置其文本容器(与 initWithFrame: 不同,后者不仅初始化接收器,还自动创建并互连其自己的文本系统对象 web)。
    系统中的每个文本视图都连接到其自己的文本容器。
NSTextView *textView;
textView = [[NSTextView alloc]
           initWithFrame:self.windowView.frame
           textContainer:textContainer];

一旦NSTextView对象初始化完毕,您就将其设为窗口的内容视图,然后显示该视图。
makeFirstResponder:消息使文本视图成为键,以便它接受击键事件。

[self.window setContentView:textView];
[self.window makeKeyAndOrderFront:nil];
[self.window makeFirstResponder:textView];

为简单起见,此代码将文本视图直接放入窗口的内容视图中。
更常见的是,将文本视图放在滚动视图内,如将NSTextView 对象放入 NSScrollView 中所述。


5、常见配置

下图让您了解如何配置四个主要文本系统类的对象 —— NSTextStorageNSLayoutManagerNSTextContainer, 和NSTextView —— 实现不同的文本处理目标。

要显示单一文本流,请按照图 3-3所示排列对象。


图 3-3 单个文本流的文本对象配置

在这里插入图片描述


NSTextView对象提供显示字形的视图,并且NSTextContainer对象 定义该视图中字形的布局区域。
通常在这种配置中,NSTextContainer对象的垂直尺寸被声明为某个非常大的值,以便容器可以容纳任意数量的文本,同时使用NSTextView定义的setVerticallyResizable:方法,将NSTextView对象 设置为围绕文本调整自身大小,并指定等于对象高度的最大高度。
然后,将文本视图嵌入到NSScrollView对象,用户可以滚动查看该文本的任何部分。

NSText``NSTextContainer

如果文本容器的区域超出了文本视图的边界,则文本周围会出现边距。
NSLayoutManager对象和此处未显示的其他对象协同工作,根据NSTextStorage对象的数据生成字形,并将它们布置在NSTextContainer对象定义的区域内。

此配置的限制在于只有一对 NSTextContainer-NSTextView
在这样的排列中,文本在 NSTextContainer 定义的区域内 不间断地流动。
此排列无法容纳分页符、多列布局和更复杂的布局。

通过使用多对 NSTextContainer-NSTextView,更复杂的布局可以进行多种安排。
例如,为了支持分页符,应用程序可以配置文本对象,如图3-4所示。


图 3-4 分页文本的文本对象配置

在这里插入图片描述


每对 NSTextContainer-NSTextView对应于文档的一页。
图 3-4中的蓝色矩形 表示您的应用程序 作为NSTextView对象 背景提供的自定义视图对象。
此自定义视图可以嵌入到NSScrollView对象中,以使用户能够滚动浏览文档的页面。

多列文档使用类似的配置,如图3-5所示。


图 3-5 多列文档的文本对象配置
在这里插入图片描述


不再有一对NSTextView-NSTextContainer对应单个页面,而是有两对 —— 页面上的每一列 各一对。
每对NSTextContainer-NSTextView控制文档的一部分。
显示文本时,字形首先在左上角视图中布局。
当该视图中没有更多空间时,NSLayoutManager对象会通知其代理它已完成容器填充。
代理可以检查是否还有更多文本需要布局,并根据需要添加另一NSLayoutManagerNSTextView
NSTextContainer对象 继续在下一个容器中布局文本,完成后通知代理,依此类推。
同样,自定义视图(表示为蓝色矩形)为这些文本列提供了画布。

您不仅可以拥有多个NSTextContainer-NSTextView对,还可以拥有访问同一文本存储的多个NSLayoutManager对象。
图 3-6说明了具有多个布局管理器的最简单安排。


图 3-6 同一文本的多个视图的文本对象配置

在这里插入图片描述


这种安排的效果是为同一文本提供多个视图。
如果用户在顶视图中更改文本,则更改会立即反映在底视图中(假设更改的位置在底视图的范围内)。

最后,复杂的页面布局要求(例如允许文本环绕嵌入的图形)可以通过使用 NSTextContainer 的自定义子类的配置来实现。
此子类定义了一个区域,该区域可调整其形状以适应图形图像并使用图3-7所示的对象配置。


图 3-7 具有自定义文本容器的文本对象配置
在这里插入图片描述


有关文本系统如何布局文本的信息,请参阅 文本布局编程指南


五、文本字段、文本视图和字段编辑器

文本字段、文本视图和字段编辑器是 Cocoa 文本系统中的重要对象,因为它们是用户与系统交互的核心。
它们提供文本输入、操作和显示。
如果您的应用程序以任何方式处理用户输入的文本,您应该了解这些对象。


1、文本字段

文本字段是从NSTextField类实例化的用户界面控件对象。
图 4-1显示了文本字段。
文本字段显示少量文本,通常(但不一定)只有一行。
文本字段为用户提供输入文本响应(例如搜索参数)的地方。
与所有控件一样,文本字段具有目标和操作,并且在编辑时会对其值执行验证。
如果值无效,它会向其目标发送特殊的错误操作消息。


图 4-1 文本字段
在这里插入图片描述


文本字段由两个类实现:NSTextFieldCell,执行大部分工作的单元格,以及NSTextField,包含该单元格的控件。
NSTextFieldCell 中的每个方法在 NSTextField 中都有一个封面。(封面是调用原始方法的同名方法。)
NSTextField对象可以有一个代理,它响应 textShouldEndEditing: 等代理方法。

默认情况下,文本字段会在编辑结束时(即用户按下 Return 键或将焦点移至另一个控件时)发送操作消息。
您可以控制文本字段的形状和布局、文本的字体和颜色、背景颜色、文本是可编辑还是只读、文本是否可选(如果是只读),以及文本超出文本字段的可见区域时是滚动还是换行。

要创建用于输入密码的安全文本字段,请使用NSSecureTextField,是 NSTextField 的一个子类。
安全文本字段用项目符号代替用户输入的字符,并且不允许剪切或复制其内容。
您可以使用stringValue方法,但用户无权访问该值。
有关安全文本字段实现的信息,请参阅 为什么使用自定义字段编辑器?

实例化文本字段的常用方法是从 Interface Builder 对象库中拖动 NSTextFieldNSSecureTextField 对象 并将其放置在应用程序用户界面的窗口中。
您可以在Inspector 窗口的 Connections 窗格中设置每个字段的nextKeyView出口,从而将文本字段在其窗口的关键视图循环中链接在一起,以便用户可以按 Tab 键将键盘焦点按照您指定的顺序从一个字段移动到下一个字段。


2、文本视图

文本视图是从NSTextView类实例化的用户界面对象。
图 4-2显示了文本视图。
文本视图通常显示按段落布局的多行文本,具有复杂排版的所有特征。
文本视图是 Cocoa 文本编辑系统的主要用户界面。
它处理用户事件以提供文本输入和修改,并显示任何字体(包括非英语语言的字体),具有任意颜色、样式和其他属性。


图 4-2 文本视图
在这里插入图片描述


Cocoa 文本系统支持文本视图和许多其他底层对象,提供文本存储、布局、字体和属性操作、拼写检查、撤消和重做、复制和粘贴、拖放、将文本保存到文件以及其他功能。
NSTextViewNSText的子类,由于历史原因,它是一个单独的类。
您无需实例化NSText,尽管它声明了您在 NSTextView 中使用的许多方法。
当您将一个NSTextView对象放入另一个NSWindow对象中时,您将拥有一个功能齐全的文本编辑器,其功能由 Cocoa 文本系统“免费”提供。


3、现场编辑

这字段编辑器是窗口中所有控件(包括文本字段)共享的单个NSTextView对象。
此文本视图对象将自身插入视图层次结构中,为当前活动的文本字段提供文本输入和编辑服务。
当用户将焦点转移到文本字段时,字段编辑器开始处理该字段的击键事件和显示。
图 4-3说明了字段编辑器与其正在编辑的文本字段的关系。


图 4-3 字段编辑器
在这里插入图片描述


由于窗口中一次只能有一个文本字段处于活动状态,因此系统每个窗口只需要一个NSTextView实例作为字段编辑器。
但是,您可以替换自定义字段编辑器,在这种情况下可能会有多个字段编辑器。
字段编辑器除了其他职责外,还负责维护选择。
因此,未编辑的文本字段通常根本没有选择。

有关字段编辑器的更多信息,请参阅 使用字段编辑器。

六、文本属性

Cocoa 文本系统处理五种文本属性:字符属性、临时属性、段落属性、字形属性和文档属性。
字符属性包括字体、颜色和下标等特征,这些特征可以与单个字符或字符范围相关联。
临时属性是仅适用于特定布局且不是持久性的字符属性。
段落属性是诸如缩进、制表符和行距等特征。
字形属性影响布局管理器呈现字形的方式,并包括诸如重打前一个字形等特征。
文档属性包括文档范围的特征,例如纸张大小、边距和视图缩放百分比。

本章简要介绍了各种类型的文本属性,并引用了更详细的文档。


1、角色属性

文本系统存储字符属性属性字符串与应用这些属性的字符一起持久存在。
文本系统的预定义字符属性控制字符的外观(字体、前景色、背景色和连字处理)及其位置(上标、基线偏移和字距调整)。

两个特殊字符属性与链接和附件有关。
链接属性指向一个 URL(封装在一个NSURL对象中)或您选择的任何其他对象。
附件属性与特殊附件字符相关联,并指向包含附加文件或内存数据的NSFileWrapper对象。

预定义字符属性NSCharacterShapeAttributeName使您能够为 Apple Type Services 在字体渲染中使用的字符形状特征设置一个值。
此功能目前用于指定中文和日文脚本中的传统形状,但字体开发人员也可以将其用于其他脚本。

预定义字符属性NSGlyphInfoAttributeName指向NSGlyphInfo对象提供一种方法来覆盖标准字形生成过程并在属性范围内替换指定的字形。


存储角色属性

属性字符串将角色属性作为键值对存储在NSDictionary对象。
键是属性名称,由标识符(NSString常量)表示,例如NSFontAttributeName
图5-1显示了带有属性字典的属性字符串应用于字符串内的范围。


图 5-1 NSAttributedString 对象的组成及其属性字典

在这里插入图片描述


从概念上讲,属性字符串中的每个字符都有一个关联的属性字典。
然而,属性字典通常适用于更长范围的字符。
NSAttributedString类提供采用字符索引并返回关联属性字典及其属性值适用范围的方法。
有关使用这些方法的更多信息,请参阅 访问属性

除了预定义的属性之外,您还可以分配任何属性键值对您希望将属性添加到字符范围内。
您可以使用NSMutableAttributedString对象的 addAttribute:value:range: 方法,将属性添加到NSTextStorage
您还可以使用addAttributes:range:方法,创建一个 包含一组自定义属性的名称和值的NSDictionary对象。
要使用自定义属性,您需要一个自定义NSLayoutManager子类,知道如何处理它们。
你的子类应该重写drawGlyphsForGlyphRange:atPoint:方法首先调用超类来绘制字形范围,然后在其上绘制您自己的属性,或者完全按照您自己的方式绘制字形。


属性修复

编辑属性字符串可能会导致不一致,必须通过属性修复来清除这些不一致。
NSMutableAttributedString 的AppKit 扩展 定义fix...方法,修复附件、字体和段落属性之间不一致的方法。
这些方法确保附件字符被删除后不会残留,字体属性仅适用于该字体中可用的字符,并且段落属性在整个段落中保持一致。

有关字符属性和属性修复的更多详细信息,请参阅 属性字符串编程指南


2、临时属性

临时属性是没有与属性字符串一起存储的字符属性。
相反,布局管理器在布局过程中分配临时属性,并仅在绘制文本时使用它们。
例如,您可以使用临时属性在编程语言中为拼写错误的单词加下划线或为关键词添加颜色。

临时属性仅影响文本的外观,而不影响文本的布局方式。
您可以使用 与常规字符属性相同的键 将临时属性存储在NSDictionary对象中,或者使用自定义属性名称(如果您有可以处理它们的NSLayoutManager子类)。
然后,使用NSLayoutManageraddTemporaryAttributes:forCharacterRange:方法添加属性:默认情况下,唯一可识别的临时属性是影响颜色和下划线的属性。
在布局期间,临时属性将取代常规字符属性。
因此,例如,如果一个字符具有存储的NSForegroundColorAttributeName值指定蓝色,并且相同标识符的临时属性指定红色,则字符将呈现为红色。

有关临时属性的更多信息,请参阅 NSLayoutManager 类参考


3、段落属性

段落属性影响布局管理器将文本行排列成页面上的段落的方式。
文本系统将段落属性封装在NSParagraphStyle类的对象中. 预定义字符属性之一的值NSParagraphStyleAttributeName,指向包含该字符范围的段落属性的NSParagraphStyle对象。
属性固定可确保每个段落中只有一个NSParagraphStyle对象与字符相关。

段落属性包括对齐、制表位、换行模式和行距(也称为行距)等特征。
文本应用程序的用户通过标尺视图控制段落属性,由NSRulerView类定义。

有关段落属性的更多详细信息,请参阅 标尺和段落样式编程主题


4、字形属性

字形是文本系统在显示器上实际绘制的字符的具体表示。
字形属性不是像字符属性那样的复杂数据结构,而只是布局管理器用于表示在渲染过程中对特定字形的特殊处理。

文本系统很少使用字形属性,应用程序应该没有理由关心它们。
尽管如此,NSLayoutManager提供处理字形属性的公共方法,因此您可以使用子类来扩展机制以在必要时处理自定义字形属性。

字形生成器在排版过程中根据需要在字形上设置内置字形属性。
在此过程中,它们在布局管理器的字形缓存中维护,但不会永久存储。
字形NSGlyphAttributeInscribe属性的两个示例 是用于布局完全对齐文本的空格的弹性属性,以及用于在字体不包含内置带变音符的字符时 在字符上绘制变音符等情况的属性。

有关字形属性的更多信息,请参阅setIntAttribute:value:forGlyphAtIndex:*NSLayoutManager 类参考*中的方法。


5、文档属性

文档属性适用于整个文档。
文档属性包括纸张大小、边距和视图缩放百分比等特征。

尽管文本系统没有存储文档属性的内置机制,但初始化方法(如initWithRTF:documentAttributes:)可以填充NSDictionary对象,该对象由RTF或HTML数据流派生的文档属性提供。

相反,如果在消息中传递对包含RTF数据的NSDictionary对象的引用,则写入RTF数据的方法(如RTF FromRange:documentAttributes:)会写入文档属性。

请参阅 RTF 文件和属性字符串以及NSAttributedString 应用程序工具包附加参考以获取更多信息。


七、字体处理

本章介绍 Cocoa 文本系统如何处理字体。
它介绍了如何在应用程序中使用字体面板、如何直接使用字体对象以及如何使用字体管理器。


1、字体面板

字体面板,也称为*“字体”窗口*,是一个用户界面对象,显示可用字体系列和样式的列表,让用户预览它们并更改用于显示文本的字体。
文本对象(例如 NSTextView)可与NSFontPanelNSFontManager对象来实现 AppKit 的字体转换系统。
默认情况下,文本对象使“字体”面板使用其选择的第一个字体或其输入属性进行更新。
它还会根据“字体”面板和“字体”菜单中的消息更改显示文本的字体。
此类更改适用于富文本对象的选定文本或输入属性,或适用于纯文本对象中的所有文本。

NSFontManager是字体转换的中心,即更改字体对象的特征,例如其大小或字体。
字体管理器接收来自字体面板的消息,并将消息发送到响应链上,以对文本对象采取行动。

通常,应用程序的字体面板会显示所有标准字体系统上可用的字体。
如果这不适合你的应用程序(例如,如果只应使用固定间距字体),你可以为对象分配一个代理来过滤可用的字体。
NSFontPanel对象 将特定字体系列 或字体添加到其列表之前,NSFontPanel对象会询问其代理 通过向代表发送fontManager:willIncludeFont:消息 确认添加。
如果代理返回TRUE(或不实现此方法),则添加字体。
如果代理返回FALSE,则不添加字体。
必须在加载主 nib 文件之前调用此方法。


创建字体面板

一般来说,你可以将字体面板的功能,以及NSFontManager对象和字体菜单添加到你的应用程序中,用户可以通过 Interface Builder 打开字体面板。

您可以通过拖动字体或格式菜单(其中包含字体子菜单)添加到应用程序的菜单中。
在运行时,将创建字体面板对象并将其挂接到字体转换系统中。
您还可以使用sharedFontPanel类方法。

您可以使用setAccessoryView:方式将自定义视图对象添加到NSFontPanel对象,允许您向字体面板添加自定义控件。
您还可以通过为应用程序的字体管理器对象分配代理来限制显示的字体(默认情况下,显示所有字体)。

如果你想要NSFontManager对象 从一些类之外,来实例化字体面板NSFontPanel,使用NSFontManager类方法setFontPanelFactory:
有关使用字体转换系统的更多信息,请参阅 手动转换字体


使用字体面板

您可以使用(NSTextViewNSText)方法 启用文本对象和字体面板之间的交互setUsesFontPanel:方法。
对于用作字段编辑器的文本视图,建议这样做, 例如。

您可以在标准文本字段以外的对象上使用“字体”面板。
NSFontManagersetAction:方法 设置 在选择新字体时,发送到第一响应者链的操作(由选择器指定)。
默认选择器是changeFont:
任何从响应链接收到此消息的对象都应该发送一个convertFont:消息返回到NSFontManager以用户指定的方式转换字体。

此示例假设仅选择了一种字体:

(void)changeFont:(id)sender
{
    NSFont *oldFont = [self font];
    NSFont *newFont = [sender convertFont:oldFont];
    [self setFont:newFont];
    return;
}

如果选择了多种字体,则changeFont:必须为每种选定的字体发送转换消息。
这对于表格视图等对象很有用,因为它们本身不会响应来自字体面板的消息。


2、使用字体对象

计算机字体是一种格式为 OpenType 或 TrueType 的数据文件,其中包含描述一组字形的信息(如 字符和字形中所述)以及字形渲染中使用的各种补充信息。
NSFont类提供用于获取和设置字体信息的接口。
NSFont 实例提供对字体特征和字形的访问。
文本系统将字符信息与字体信息相结合,以选择文本布局期间使用的字形。
您可以通过将字体对象传递给接受它们作为参数的方法来使用它们。
字体对象是不可变的,因此可以安全地在应用中的多个线程中使用它们。

您不能使用allocinit方法创建字体对象;相反,您可以使用fontWithName:matrix:fontWithName:size: 等方法来查找可用字体并根据需要更改其大小或矩阵。
这些方法检查具有指定特征的现有字体对象,如果存在则返回该对象。
否则,它们将查找请求的字体数据并创建适当的对象。
您还可以使用字体描述符来创建字体。
请参阅 字体描述符。

NSFont还定义了许多用于指定标准系统字体的方法,例如systemFontOfSize:userFontOfSize:messageFontOfSize:
要请求这些标准字体的默认大小,请传递0或负数 作为字体大小。
标准系统字体方法在查询标准字体变体中列出。


字体描述符

NSFontDescriptor类 实例化的字体描述符提供了一种使用属性字典描述字体的方法。
然后可以使用字体描述符来创建或修改NSFont对象。
具体来说,您可以从字体描述符中创建NSFont对象,可以从NSFont对象中获取描述符,还可以更改描述符并使用它来创建新的字体对象。
您还可以使用字体描述符来指定应用提供的自定义字体。

字体描述符提供字体匹配功能,通过该功能,您可以创建仅包含系列名称的字体描述符来部分描述字体。
然后,您可以使用 matchingFontDescriptorsWithMandatoryKeys: 查找系统中具有匹配系列名称的所有可用字体。

字体描述符可以存档,这比存档字体对象更可取,因为字体对象是不可变的,并且可以访问安装在特定系统上的特定字体。
另一方面,字体描述符根据字体的特征和属性来描述字体,并在运行时提供对当前可用的与这些属性匹配的字体的访问。

也就是说,您可以使用字体描述符查询系统中与特定属性匹配的可用字体,然后创建与这些属性(如名称、特征、语言和其他功能)匹配的NSFont实例。
例如,您可以使用字体描述符检索与给定字体系列名称匹配的所有字体,使用 PostScript 系列名称(由 CSS 标准定义),如清单6-1所示。


例 6-1 字体系列名称匹配

NSFontDescriptor *helveticaNeueFamily =
    [NSFontDescriptor fontDescriptorWithFontAttributes:
        @{ NSFontFamilyAttribute: @"Helvetica Neue" }];
NSArray *matches =
    [helveticaNeueFamily matchingFontDescriptorsWithMandatoryKeys: nil];

所示的 matchingFontDescriptorsWithMandatoryKeys: 方法 返回系统上所有 Helvetica Neue 字体的字体描述符数组,例如HelveticaNeueHelveticaNeue-MediumHelveticaNeue-LightHelveticaNeue-Thin等等。
(字体册应用程序在字体信息窗口中显示给定 OS X 安装中可用的字体的 PostScript 字体和字体系列名称。)


查询字体规格

NSFont定义了许多用于访问字体度量信息(当该信息可用时)的方法。
诸如boundingRectForGlyph:boundingRectForFontxHeight等方法都与标准字体度量信息相对应。
图 6-1显示了字体度量如何应用于字形尺寸,表 6-1列出了与度量相关的方法名称。
有关更具体的信息,请参阅各种方法的描述。


图 6-1 字体规格
在这里插入图片描述


Table 6-1 Font metrics and related NSFont methods

字体度量方法
AdvancementadvancementForGlyph:maximumAdvancement
X-heightxHeight
Ascentascender
Bounding rectangleboundingRectForFontboundingRectForGlyph:
Cap heightcapHeight
Line heightdefaultLineHeightForFontpointSizelabelFontSizesmallSystemFontSizesystemFontSizesystemFontSizeForControlSize:
Descentdescender
Italic angleitalicAngle

查询标准字体变体

使用NSFont方法如表6-2所示,可以查询所有标准字体变化。
请求标准字体的默认字体大小,您可以明确传入默认大小(从类方法,例如systemFontSizelabelFontSize 获取),也可以传入0或负值。


Table 6-2 Standard font methods

字体方法
系统字体[NSFont systemFontOfSize:[NSFont systemFontSize]]
强调系统字体[NSFont boldSystemFontOfSize:[NSFont systemFontSize]]
小系统字体[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]
强调小系统字体[NSFont boldSystemFontOfSize:[NSFont smallSystemFontSize]]
迷你系统字体[NSFont systemFontSizeForControlSize: NSMiniControlSize]
强调迷你系统字体[NSFont boldSystemFontOfSize:[NSFont systemFontSizeForControlSize: NSMiniControlSize]]
应用程序字体[NSFont userFontOfSize:-1.0]
应用等宽字体[NSFont userFixedPitchFontOfSize:-1.0]
标签字体[NSFont labelFontOfSize:[NSFont labelFontSize]]

字符、字形和布局管理器

字符是与书面语言单位相对应的概念实体。
一般来说,字形是字符的具体渲染图像。
(印刷概念对字符和字形进行了更详细的讨论。)

在英语中,字符和字形之间通常存在一一映射,但情况并非总是如此。
例如,字形“ö”可以是两个字符的结果,一个表示基本字符“o”,另一个表示变符号“¨”。
文字处理器的用户可以按一次箭头键将插入点从“ö”字形的一侧移动到另一侧;但是,字符流中的当前位置必须增加 2 才能说明构成单个字形的两个字符。

因此,文本系统必须管理两个相关但不同的数据流:字符流(及其属性)和从这些字符派生的字形流。
NSTextStorage 对象存储属性字符,NSLayoutManager对象存储派生字形。
找到这两个流之间的对应关系是布局管理器的职责。
例如,当用户选择一段文本时,布局管理器必须使用屏幕上显示的字形来确定哪个字符范围与选择相对应。

删除字符时,可能需要重新绘制某些字形。
例如,如果用户从单词“feel”中删除字符“ee”,则“f”和“l”现在相邻,可以用“fl”连字而不是两个字形“f”和“l”来表示。
NSLayoutManager对象指示字形生成器对象根据需要生成新字形。
重新生成字形后,必须布局和显示文本。
布局管理器与NSTextContainer对象 和文本系统的其他对象 一起工作,确定每个字形在文本视图中出现的位置。
最后,文本视图呈现文本。

由于NSLayoutManager对象是文本系统操作的核心,因此它还充当系统各个组件共享的信息存储库。
有关 NSLayoutManager 的更多信息,请参阅 NSLayoutManager 类参考文本布局编程指南


获取字形的视图坐标

字形位置相对于它们所在的线段的边界矩形的原点进行计算。
要获取字形线段在其容器坐标中的矩形,请使用NSLayoutManagerlineFragmentRectForGlyphAtIndex:effectiveRange:方法。
然后将该矩形的原点 添加到由locationForGlyphAtIndex:返回的字形的位置,以获取容器坐标中的字形位置。

来自CircleView 的示例代码项目的以下代码片段说明了此技术。

usedRect = [layoutManager usedRectForTextContainer:textContainer];
NSRect lineFragmentRect = [layoutManager lineFragmentRectForGlyphAtIndex:glyphIndex
                            effectiveRange:NULL];
NSPoint viewLocation, layoutLocation = [layoutManager
                            locationForGlyphAtIndex:glyphIndex];
// Here layoutLocation is the location (in container coordinates) where the glyph was laid out.
layoutLocation.x += lineFragmentRect.origin.x;
layoutLocation.y += lineFragmentRect.origin.y;


3、使用字体管理器

任何记录用户可以更改字体的对象都应该告知字体管理器当它成为第一响应者时,它选择的字体是什么并且每当其选择发生变化时,它是第一个响应者。
该对象通过向共享字体管理器发送一个setSelectedFont:isMultiple:消息。
它应该传递所选的第一个字体,以及指示是否有多个字体的标志。

字体管理器使用此信息来更新字体面板和字体菜单以反映所选字体。
例如,假设字体为 Helvetica Oblique 12.0 点。
在这种情况下,字体面板选择该字体并显示其名称;字体菜单在其斜体命令前添加一个复选标记;如果没有可用的 Helvetica 粗体变体,则粗体菜单项将被禁用;等等。


创建字体管理器

您通常会设置字体管理器和字体菜单使用界面生成器但是,您也可以通过获取共享字体管理器实例并让其在运行时创建标准字体菜单,以编程方式执行此操作,如下例所示:

NSFontManager *fontManager = [NSFontManager sharedFontManager];
NSMenu *fontMenu = [fontManager fontMenu:YES];

然后,您可以将“字体”菜单添加到应用程序的菜单中。
一旦安装了“字体”菜单,您的应用程序就会自动获得“字体”菜单和“字体”面板的功能。


处理字体变化

用户通常通过操作字体面板来更改所选字体(也称为“字体”窗口)和“字体”菜单。
这些对象通过向字体管理器发送操作消息来启动预期的更改. 改变字体的操作方法有四种:

  • addFontTrait:
  • removeFontTrait:
  • modifyFont:
  • modifyFontViaPanel:

前三个命令使字体管理器查询消息的发送者,以确定要添加或删除哪些特性,或者如何修改字体。
最后一个命令使字体管理器使用字体面板中的设置来修改字体。
字体管理器记录此信息并在以后的请求中使用它来转换字体。

当字体管理器收到addFontTrait:removeFontTrait:消息时,它会向发送者发送一条tag消息,将返回值解释为特征掩码,以供使用convertFont:toHaveTrait:或者convertFont:toNotHaveTrait:,如 手动转换字体中所述。
例如,字体菜单命令 Italic 和 Bold 分别具有特征掩码值NSItalicFontMaskNSBoldFontMask
有关特征掩码值列表,请参阅 NSFontManager 类参考 中的常量部分。

当字体管理器收到modifyFont:消息时,它会向发送者发送一条tag消息,并将返回值解释为要执行的一种特定转换,具体操作请参见手动转换字体中描述的各种转换方法。
例如,标签值为 SizeUpFontAction 的按钮会导致字体管理器的convertFont:``NSFont方法增加作为参数传递的对象的大小。
请参阅NSFontManagermodifyFont: 方法以获取转换标签值的列表。

对于modifyFontViaPanel:,字体管理器向应用程序的字体面板发送panelConvertFont:消息。
字体面板则使用字体管理器根据用户的选择转换提供的字体。
例如,如果用户在字体面板中只选择字体系列(可能是 Helvetica),那么对于提供给 panelConvertFont: 的任何字体,都只会更改系列:Courier Medium 10.0 点变为 Helvetica Medium 10.0 点,Times Italic 12.0 点变为 Helvetica Oblique 12.0 点。

字体管理器响应字体变化 通过发送一个changeFont:动作方法,动作消息沿响应者链向上传递。
接收此消息的文本承载对象应让字体管理器通过调用convertFont:针对每种字体,并使用NSFont对象返回。
convertFont:方法 使用字体更改操作方法记录的信息,例如addFontTrait:,适当地修改所提供的字体。
(没有办法明确设置字体更改操作或特征;相反,您可以使用手动转换字体中描述的方法。)

这个简单的例子假设选择中只有一种字体:

- (void)changeFont:(id)sender
{
    NSFont *oldFont = [self selectionFont];
    NSFont *newFont = [sender convertFont:oldFont];
    [self setSelectionFont:newFont];
    return;
} 

大多数带有文本的对象必须扫描选择范围,以查找具有不同字体的范围,并为每个字体调用convertFont:


手动转换字体

NSFontManager定义了许多用于显式转换的方法转换方法可以用于转换字体的特定特征和特性。
表 6-3列出了这些转换方法。


方法行为
convertFont:toFace:更改所提供字体的基本设计。
需要完整指定的字体名称,例如Times-RomanHelvetica-BoldOblique
convertFont:toFamily:改变所提供字体的基本设计。
只需要一个字体系列名称,例如TimesHelvetica
convertFont:toHaveTrait:使用特征掩码添加单一特征,例如斜体、粗体、压缩或扩展。
convertFont:toNotHaveTrait:使用特征掩码删除单个特征,例如斜体、粗体、压缩或扩展。
convertFont:toSize:返回所请求大小的字体,其其他所有特征与原始字体相同。
convertWeight:ofFont:根据布尔标志增加或减少所提供字体的粗细。
字体粗细通常由一系列名称表示,这些名称可能因字体而异。
有些字体从浅到中到粗,而有些字体有 Book、SemiBold、Bold 和 Black。
此方法提供了一种统一的方式来增加和减少任何字体的粗细。

每种方法都返回所提供字体的转换版本,如果无法转换,则返回原始字体。

字体转换的默认实现非常保守,只有在不影响其他特征或方面的情况下才会进行更改。
例如,如果您尝试通过添加 Bold 特征来转换 Helvetica Oblique 12.0 点,并且只有 Helvetica Bold 可用,则不会转换该字体。
您可以创建一个NSFontManager子类 并重写转换方法以执行不太保守的转换,也许在这种情况下使用 Helvetica Bold 并丢失 Oblique 特征。

除了字体转换方法之外,NSFontManager定义fontWithFamily:traits:weight:size: 构造具有给定特征集的字体。
如果您不想创建 NSFontManager 的子类,则可以使用此方法自行执行近似字体转换。


设置字体样式和特征

本节介绍如何以编程方式在属性字符串中设置字体样式(例如粗体或斜体)和字体属性(例如下划线)。

下划线是一种属性,可以使用常量轻松设置在属性字符串上,使用NSUnderlineStyleAttributeName 常量,如 NSMutableAttributedString 类参考 描述,可以使用如下:

- (void)addAttribute:(NSString *)name value:(id)value range:(NSRange)aRange

给name参数 传递NSUnderlineStyleAttributeName,其值为 [NSNumber numberWithInt:1]

与下划线不同,粗体和斜体是字体的特性,因此您需要使用字体管理器实例将字体转换为所需的特性,然后将字体属性添加到可变属性字符串中。
对于名为的可变属性字符串attributedString,请使用以下技术:

NSFontManager *fontManager = [NSFontManager sharedFontManager];
unsigned idx = range.location;
NSRange fontRange;
NSFont *font;
 
while (NSLocationInRange(idx, range)){
    font = [attributedString attribute:NSFontAttributeName atIndex:idx
                            longestEffectiveRange:&fontRange inRange:range];
    fontRange = NSIntersectionRange(fontRange, range);
    [attributedString applyFontTraits:NSBoldFontMask range:fontRange];
    idx = NSMaxRange(fontRange);
} 

如果你的可变属性字符串 实际上是一个NSTextStorage对象,请将此代码放在beginEditingendEditing呼叫。


检查字体

除了转换字体之外,NSFontManager提供有关应用程序可用的字体以及任何给定字体的特征的信息。
availableFonts方法返回所有可用字体的名称数组。
availableFontNamesWithTraits:方法根据字体特征掩码过滤可用的字体。

有三种方法可以检查单个字体。
fontNamed:hasTraits:``true如果字体与提供的特征掩码匹配,则方法返回。
traitsOfFont:方法返回给定字体的特征掩码。
weightOfFont:方法返回字体粗细的近似等级,范围为 0-15,其中 0 表示最细,5 表示正常或书籍粗细,9 表示相当于粗体,15 表示最粗(通常称为黑色或超黑)。


自定义字体转换系统

如果您需要通过创建NSFontManagerNSFontPanel的子类,来自定义字体转换系统,则必须在创建共享字体管理器或共享字体面板之前,用setFontManagerFactory:setFontPanelFactory:消息通知NSFontManager类此更改。
这些方法将您的类记录为第一次请求字体管理器或字体面板时要实例化的类。
如果只需要在字体面板中添加一些自定义控件,则可以避免使用子类。
在这种情况下,您可以调用NSFontPanel方法setAccessoryView:在其字体浏览器下方添加NSView对象。

如果您提供自己的字体菜单,则应使用setFontMenu:方法向字体管理器注册。
字体管理员负责验证字体菜单项,并根据所选字体更改其标题和标记。
例如,当所选字体为斜体时,字体管理器会在斜体字体菜单项中添加一个复选标记,并将其标记更改为UnitalicMask
字体菜单的项目应该使用适当的操作方法和标记,如表6-4所示。


Table 6-4 Font menu item actions and tags

字体菜单项行动标签
斜体addFontTrait:ItalicMask
粗体addFontTrait:BoldMask
更重modifyFont:HeavierFontAction
更大modifyFont:SizeUpFontAction

另请参阅以下文档:属性字符串编程指南 描述了NSAttributedString对象,这些对象管理与字符串或单个字符相关的属性集,例如字体和字距调整。
文本布局编程指南 描述了 Cocoa 文本系统 如何将文本字符串、字体信息和页面规范转换为放置在页面上特定位置的字形行,适合显示和打印。


八、文本编辑

本章介绍了在执行文本编辑时控制 Cocoa 文本系统行为的方法。
文本编辑是通过与文本对象交互来修改文本字符或属性。
通常,编辑是通过用户直接使用文本视图执行的,但也可以通过与文本存储对象的编程交互来完成。
本文档还讨论了将键盘事件转换为命令和文本输入的文本输入系统。

Cocoa 文本系统实现了复杂的编辑机制,允许输入和修改复杂的文本字符和样式信息。
如果您的代码需要连接到该机制来修改该行为,那么了解该机制非常重要。

文本系统提供了许多控制点您可以在其中自定义编辑行为:

  • 文本系统类提供了控制执行编辑的多种方式的方法。
  • 您可以通过 Cocoa 的通知和代理机制实现更多的控制。
  • 在文本系统的功能不适合的极端情况下,您可以用自定义子类替换文本视图。

1、编辑环境

文本编辑由文本视图执行对象。
通常,文本视图是NSTextView或子类。
文本视图为文本系统提供前端。
它显示文本、处理编辑文本的用户事件,并协调编辑过程所需的对存储文本的更改。
NSTextView实现执行编辑、管理选择和处理影响文本布局和显示的格式属性的方法。

NSTextView有许多方法可以控制用户可用的编辑行为。
例如,NSTextView允许你授予或拒绝用户选择或编辑其文本的能力,使用setSelectable:setEditable:方法。
NSTextView 还实现了由NSText定义的 setRichText:setImportsGraphics:方法,区别纯文本和富文本。
请参阅 文本系统用户界面层编程指南NSTextView 类参考NSText 类参考 以了解更多信息。

可编辑文本视图可以在两种不同的编辑模式下运行:作为普通文本编辑器或字段编辑器。
字段编辑器是应用程序中窗口的多个文本字段共享的单个文本视图实例。
这种共享可以提高性能。
当文本字段成为第一响应者时,窗口会将字段编辑器插入响应者链中的位置。
普通文本编辑器接受 Tab 和 Return 字符作为输入,而字段编辑器将 Tab 和 Return 解释为结束编辑的提示。
NSTextViewsetFieldEditor: 方法控制此行为。

有关字段编辑器的更多信息,请参阅 使用字段编辑器


2、按键输入消息序列

当您想要修改 Cocoa 编辑文本的方式时,了解定义编辑机制的消息序列会很有帮助,这样您就可以选择最合适的点来添加自定义行为。

文本视图收到按键事件时,NSResponder调用的消息序列涉及声明的四种方法。
当用户按下某个键时,操作系统会处理某些保留的键事件,并将其他事件发送到NSApplication对象,它将 Command 键事件作为等效键处理。
未处理的键事件由应用程序对象发送到键窗口,它处理映射到键盘导航操作的按键事件(例如 Tab 键将焦点移到下一个视图),并将其他按键事件发送给第一响应者。
图 7-1说明了此序列。


图 7-1 按键事件处理

在这里插入图片描述


如果第一响应者是文本视图,则按键事件进入文本系统。
按键窗口向文本视图发送keyDown:,以事件作为参数的消息。
keyDown:方法 将事件传递给handleEvent:,它将字符输入发送到输入上下文进行键绑定和解释。
作为响应,输入上下文发送insertText:replacementRange:setMarkedText:selectedRange:replacementRange:, 或者doCommandBySelector:到文本视图。
图 7-2说明了文本输入事件处理的顺序。


图 7-2 输入上下文键绑定和解释

在这里插入图片描述


文本输入系统使用一个字典属性列表,称为键绑定字典,用于解释键盘事件然后将它们传递给输入法套件框架来映射到字符。

在处理键盘事件期间,事件会经过NSMenu对象,然后通过keyDown:方法。
NSResponder 类提供的方法的默认实现 将消息传播到响应链 直到被重写的keyDown:实现停止传播。
通常,NSResponder子类 可以选择处理某些键并忽略其他键(例如,在游戏中)或发送handleEvent:消息到其输入上下文。

输入上下文 检查事件是否与用户的按键绑定字典中的任何按键匹配。
按键绑定字典将按键(包括其修饰键)映射到方法名称。
例如,默认按键绑定字典将^d(Control-D)映射到方法名称deleteForward:
如果键盘事件在字典中,则输入上下文调用文本视图的doCommandBySelector:方法与与字典条目相关联的选择器。

如果输入上下文无法将键盘事件与键绑定字典中的条目匹配,它会将事件传递给输入法套件以映射到字符。

标准键绑定字典位于/System/Library/Frameworks/AppKit.framework/Resources/StandardKeyBinding.dict文件中。
您可以通过在 ~/Library/KeyBindings/DefaultKeyBinding.dict 路径中提供字典文件来完全覆盖标准字典。
但是,不支持动态定义 自定义键绑定(即在应用程序运行时)。

有关文本输入键事件处理的更多信息,请参阅 《Cocoa 事件处理指南》中的“文本系统默认值和键绑定” 。

当文本视图有足够的信息 来指定对其文本的实际更改时,它会向其NSTextStorage对象 发送编辑信息 来实现更改。
更改文本存储中的字符和属性信息的方法 NSMutableAttributedString 对象在 NSTextStorage 超类中声明,并且它们依赖于两个原始方法replaceCharactersInRange:withString:setAttributes:range:
文本存储对象随后会通知其布局管理器有关更改,以便在必要时启动字形生成和布局,并在处理编辑之前和之后发布通知并发送代理消息。
有关文本视图、文本存储和布局管理器对象交互的更多信息,请参阅 文本布局编程指南


3、拦截关键事件

本节介绍如何捕获关键事件文本视图接收到该事件,以便您可以修改结果。
它还解释了文本视图接收到按键事件时发生的消息序列。

例如,如果您希望用户能够在文本字段中插入换行符,则需要拦截按键事件。
默认情况下,文本字段只包含一行文本。
按 Enter 或 Return 键会导致文本字段结束编辑并将其动作消息发送给其目标,因此您需要修改行为。

您可能还希望拦截文本视图中的按键事件,以执行与在视图显示的文本中简单地输入字符不同的操作,例如更改内存缓冲区的内容。

在这两种情况下,您都需要处理文本视图对象,这对于文本视图的情况来说很明显,但对于文本字段来说则不那么明显。
文本字段中的编辑由一个称为字段编辑器NSTextView对象执行,由属于一个窗口的所有文本字段共享。

当文本视图接收到键事件时,它会将字符输入发送到输入上下文以进行键绑定和解释。
作为响应,输入上下文将insertText:replacementRange:或者doCommandBySelector:发送到文本视图,具体取决于键事件是表示要插入的文本还是要执行的命令。
输入上下文还可以发送setMarkedText:selectedRange:replacementRange:消息,以在文本视图的关联文本存储对象中设置标记文本。当文本视图接收到键事件时调用的消息序列在 按键输入消息序列 中有更详细的描述。

使用标准键绑定时,Enter或Return字符会导致文本视图接收doCommandBySelector:,选择器为insertNewline:,可以有两个结果之一。
如果文本视图不是字段编辑器,则文本视图的insertText:replacementRange:方法 会插入换行符。
如果文本视图是字段编辑器,例如当用户编辑文本字段时,文本视图将终止编辑。
通过调用setFieldEditor:,可以使文本视图以任何一种方式运行。

尽管可以通过子类化文本视图并重写insertText:replacementRange:doCommandBySelector: 来更改文本视图的行为,但更好的解决方案是在文本视图的代理中处理事件。
代理可以通过实现 textView:shouldChangeTextInRange:replacementString:方法 来控制用户对文本的更改。

要处理不插入文本的键击,代理可以实现textView:doCommandBySelector:方法。

例如,为了区分Enter和Return,代理可以测试用doCommandBySelector:传递的选择器。
如果是@selector(insertNewline:),则可以将currentEvent发送到NSApp对象,以确保该事件是一个按键事件,如果是,还可以确定按下了哪个按键。


4、文本视图代理

代理为修改编辑行为提供了一种强大的机制,因为您可以在代理中实现方法,然后这些方法 可以代替文本视图 执行编辑命令,这种技术称为实现的代理。
每当其代理从输入上下文接收到doCommandBySelector:消息时,NSTextView都会向其发送textView:doCommandBySelected:消息,从而为其代理提供处理命令的机会。
如果代理实现了此方法并返回YES,则文本视图不再执行任何操作;如果代理返回,则文本视图必须尝试自己执行命令。

在文本视图对其文本进行任何更改之前,它会向其代理发送一条textView:shouldChangeTextInRange:replacementString:消息,该消息返回一个布尔值。
(与所有代理消息一样,只有当代理实现该方法时,它才会发送消息。)
此机制为代理提供了控制与文本视图相关联的文本存储对象中的字符和属性数据的所有编辑的机会。


5、文本视图代理消息和通知

一个 NSTextView对象 可以有一个代理,通知某些操作或文本状态的待定更改。
代理可以是您选择的任何对象,一个代理可以控制多个NSTextView对象(或多个连接对象系列NSTextView)。
图 7-3 说明了 代理 接收来自 NSTextView对象的代理消息 textView:shouldChangeTextInRange:replacementString: 的活动。


图 7-3 NSTextView 对象的代理

在这里插入图片描述


NSTextDelegate 协议参考 和 *NSTextViewDelegate 协议参考*描述了代理可以接收的代理消息。
只有代理实现了该方法,代理对象才会发送消息。


全部NSTextView 附着于同一NSLayoutManager,共享同一个代理。
设置一个这样的文本视图的代理会为所有其他文本视图设置代理。
代理消息将发送者的 id 作为参数传递。

注意: 对于NSTextView附加到同一NSLayoutManager对象的多个对象,参数id是通知文本视图的参数,它是NSTextView共享NSLayoutManager对象的第一个对象。
NSTextView对象负责在适当的时间发布通知。

通知发布者NSTextView是:

  • NSTextDidBeginEditingNotification
  • NSTextDidEndEditingNotification
  • NSTextDidChangeNotification
  • NSTextViewDidChangeSelectionNotification
  • NSTextViewWillChangeNotifyingTextViewNotification

对于观察者来说,注册这些通知中的 最后一个通知尤为重要。
如果NSTextView在一系列连接NSTextView对象的开头 添加了一个新对象,它将成为新的通知文本视图。
它无法访问哪些对象正在观察其文本对象组,因此它会发布一个 NSTextViewWillChangeNotifyingTextViewNotification ,这允许所有这些观察者从旧的通知文本视图中取消注册,并重新注册到新的通知文本视图中。
有关更多信息,请参阅 NSTextView 类参考 中此通知的描述。


文本字段(即“NSTextField”的实例,而不是“NSTextView”的实例)也可以使用委托来控制其编辑行为。
其中一种方法是让文本字段本身指定一个委托。
通常,您可以在界面生成器中通过控件从文本字段对象拖动到委托对象来完成此操作,但也可以在运行时通过向文本字段发送“setDelegate:”消息来完成,例如,在“wakeFromNib”方法中。
委托必须响应“NSTextFieldDelegate”协议(采用“NSControlTextEditingDelegate“协议)定义的消息。
除了“NSControlTextEditingDelegate”协议定义的方法外,文本字段委托还可以响应“NSControl”的委托方法。

作为文本字段的委托如何控制其编辑行为的示例,您可以通过让其委托实现委托方法control:textView:completes:forPartialWordRange:indexOfSelectedItem:来禁用文本字段中的文本完成,只需返回nil

您可以通过委派自定义文本字段中的编辑行为的另一种方法涉及字段编辑器,这是一个“NSTextView”对象,用于处理窗口中所有文本字段的实际编辑。
字段编辑器会自动将正在编辑的任何文本字段指定为其委托,因此您可以通过实现“NSTextDelegate”和“NSTextViewDelegate“协议定义的委托方法,将文本字段的特殊编辑行为封装为文本字段本身。
有关通过字段编辑器发送的委托消息和通知控制文本字段编辑行为的信息,请参阅使用字段编辑器的委托和通知.


##同步编辑

编辑过程包括对各种对象的复杂交互进行仔细的同步。
文本系统协调事件处理、数据修改、响应链管理、字形生成和布局,以保持文本数据模型的一致性。

系统向委托人和观察者提供了一组丰富的通知,使您的代码能够与此逻辑交互,如文本查看委托消息和通知 中所述。


###批量编辑模式

如果您的代码需要直接修改文本备份存储,则应使用批量编辑模式;也就是说,将“NSMutableAttributedString”方法“beginEditing”和“endEditing”之间的更改括起来。
虽然这种括号不是绝对必要的,但这是一种很好的做法,如果你连续进行多个更改,这对效率很重要。
NSTextView使用beginEditingendEditing方法来同步其编辑活动,并且您可以直接使用这些方法来控制通知给代理、观察者和相关布局管理器的时间。
当“NSTextStorage”对象处于批编辑模式时,在收到“endEditing”消息之前,它不会通知布局管理器任何编辑更改。

“编辑开始”意味着即将对文本后备存储(对于文本视图为“NSTextStorage”,对于单元格为单元格值)进行一系列修改。
将编辑置于“beginEditing”和“endEditing”之间会锁定文本存储,以确保文本修改是原子事务。

“编辑结束”意味着后台存储在修改后处于一致状态。
在单元格(如“NSTextFieldCell”对象,控制文本字段中的文本编辑)中,编辑结束时与字段编辑器退出第一响应者状态一致,这会触发字段编辑器及其父单元格的内容同步。

此外,当文本视图完成对其后备存储的修改时,无论其第一响应者状态如何,它都会发送“NSTextDidEndEditingNotification”。
例如,当在“查找”窗口中单击“全部替换”按钮时,即使文本视图不是第一个响应者,它也会发出此通知。

**重要提示:**在“beginEditing”和“endEditing”消息之间调用布局管理器的任何导致布局的方法都会引发异常。
*NSLayoutManager类参考*和“NSLayoutManager.h”头文件指示哪些方法导致布局。

清单7-1说明了一种情况,其中NSText方法scrollRangeToVisible:强制进行布局并引发异常。

将字符范围滚动到可见性需要在该范围内完成布局,以便文本视图可以知道该范围的位置。
但在清单7-1中,文本存储处于批量编辑模式。
它处于不一致的状态,因此布局管理器此时无法进行布局。
将“scrollRangeToVisible:”调用移到“endEditing”之后可以解决问题。

如果在文本视图中实现新的用户操作,还需要执行其他操作,例如更改文本的菜单操作或键绑定方法。
例如,您可以使用NSTextView方法setSelectedRange:,根据命令执行的更改类型,使用NSTxtView方法 rangeForUserTextChange rangeForUserCharacterAttributeChange rangeFor UserParagraphAttributeChange的结果来修改选定的字符范围。
例如,rangeForUserParagraphAttributeChange返回包含原始选择的整个段落——如果您的操作修改了段落属性,则是受影响的范围。
此外,您应该在进行更改之前调用“textView:shouldChangeTextInRange:replacementString:”,然后调用“didChangeText”。
这些操作确保更改正确的文本,并确保系统向文本视图的委托发送正确的通知和委托消息。
参见[Subclassing NSTextView](https://developer.apple.com/library/archive/documentation/TextFonts/Conceptual/CocoaTextArchitecture/TextEditing/TextEditing.html#//apple_ref/doc/uid/TP40009459-CH3-SW16)以获取更多信息。


###强制结束编辑

在某些情况下,您可能需要强制文本系统以编程方式结束编辑,以便根据发送的通知采取一些操作。
在这种情况下,您不需要修改编辑机制,只需刺激其正常行为即可。

要强制结束文本视图中的编辑,并随后向其代理发送“textDidEndEdition:”消息,您可以观察窗口的“NSWindowDidResignKeyNotification”通知。
然后,在observer方法中,将“makeFirstResponder:”发送到窗口,以在窗口处于活动状态时完成正在进行的任何编辑。
否则,当前正在编辑的控件仍然是窗口的第一个响应程序,不会结束编辑。

清单7-2展示了“textDidEndEdition:”委托方法的一个实现,该方法结束了在“NSTableView”子类中的编辑。
默认情况下,当用户在表视图中编辑单元格并按Tab键或Return键时,字段编辑器将结束当前单元格中的编辑,并开始编辑下一个单元格。
在这种情况下,如果用户按Return键,则希望完全结束编辑。
这种方法可以区分用户按下了哪个键;对于Tab,它执行正常行为,而对于Return,它通过使窗口成为第一个响应程序来强制完全结束编辑。

例7-2强制结束编辑

- (void)textDidEndEditing:(NSNotification *)notification {
    if([[[notification userInfo] valueForKey:@"NSTextMovement"] intValue] ==
                        NSReturnTextMovement) {
        NSMutableDictionary *newUserInfo;
        newUserInfo = [[NSMutableDictionary alloc]
                            initWithDictionary:[notification userInfo]];
        [newUserInfo setObject:[NSNumber numberWithInt:NSIllegalTextMovement]
                                forKey:@"NSTextMovement"];
        notification = [NSNotification notificationWithName:[notification name]
                                object:[notification object]
                                userInfo:newUserInfo];
        [super textDidEndEditing:notification];
        [[self window] makeFirstResponder:self];
    } else {
        [super textDidEndEditing:notification];
    }
}



程序化设置焦点和选择

通常,用户单击窗口中的视图对象以设置焦点或第一响应者状态,以便随后的键盘事件最初转到该对象。
同样,用户通常通过在视图中拖动鼠标来创建选择。
但是,您可以通过编程方式设置焦点和选择。

例如,如果您有一个包含文本视图的窗口,并且您希望该文本视图成为第一个响应者,并且插入点位于当前文本视图中任何文本的开头,则需要对该窗口和文本视图的引用。
如果这些引用分别是窗口文本视图,则可以使用以下代码设置焦点和插入点,这只是一个零长度的选择范围:

[theWindow makeFirstResponder: theTextView];
[theTextView setSelectedRange: NSMakeRange(0,0)];

当符合NSTextInputClient协议的对象成为键窗口中的第一响应者时,其NSTextInput Context对象变为活动的,并绑定到活动的文本输入源,例如字符调色板、键盘和输入方法。

无论选择是以编程方式设置的还是由用户设置的,都可以使用selectedRange方法获取当前选择的字符范围。
NSTextView 通过对其应用一组特殊的属性来指示其选择。
selectedTextAttributes方法返回这些属性,而setSelectedTextAttributes:设置它们。

当响应用户输入更改选择时,NSTextView对象调用其自己的setSelectedRange:affinity:stillSelecting:方法。
第一个参数是要选择的范围。
第二个称为选择亲和性,当定义所选范围的两个字形不相邻时,确定插入点显示在哪一个字形附近。
它通常用于选定线换行的位置,以将插入点放置在一行的末尾或下一行的开头。
您可以使用selectionAffinity方法获取当前有效的选择亲和性。
最后一个参数表示选择是否仍在更改过程中;委托和任何观察者都不会被通知选择中的更改,直到用NO对此参数调用该方法。

影响选择行为的另一个因素是选择粒度:是否选择字符、单词或整个段落。
这通常由初始鼠标点击次数决定;例如,双击启动单词级别选择。
NSTextView使用其selectionRangeForProposedRange:granularity:方法决定在输入跟踪期间更改选择的程度。`。

与输入管理相关的选择的另一个方面是标记文本的范围。
当输入上下文解释键盘输入时,它可以用一种特殊的方式标记不完整的输入。
文本视图显示的标记文本与所选内容不同,使用的临时属性仅影响显示,而不影响布局或存储。
例如,NSTextView使用标记的文本来显示组合键,如Option-E,它将一个锐音符置于下一个输入的字符之上。
当用户键入Option-E时,文本视图在黄色突出显示框中显示一个锐音符,表示它是标记的文本,而不是最终输入。
当用户键入下一个字符时,文本视图将其显示为单个重音字符,标记的文本高亮显示将消失。
markdRange方法返回任何标记文本的范围,markdTextAttributes返回用于突出显示标记文本的属性。
您可以使用setMarkedTextAttributes:更改这些属性。


6、子类NSTextView

直接使用NSTextView是与文本系统交互的最简单方式,其委托机制提供了一种非常灵活的方式来修改其行为。
在委派不提供所需行为的情况下,可以将NSTextView划分为子类。

**注意:**要修改编辑行为,您的第一个方法应该是通知或委派,而不是子类化。
从子类化NSTextView和重写keyDown:开始可能很诱人,但这通常是不合适的,除非您真的需要在输入管理或键绑定之前处理原始键事件。
在大多数情况下,更适合使用文本视图委派方法之一或文本视图通知,如文本视图委派消息和通知 中所述。

文本系统要求NSTextView子类遵守某些行为规则,NSTextView提供了许多方法来帮助子类遵守这些规则。
其中一些方法旨在被覆盖,以便将信息和行为添加到基本基础结构中。
当子类定义自己的行为时,有些是作为基础结构的一部分调用的。


正在更新状态

NSTextView会随着所选内容的更改自动更新字体窗口和标尺。
如果您将任何新的字体或段落属性添加到NSTextView的子类中,则需要覆盖执行此更新的方法以考虑添加的信息。
updateFontPanel方法使字体窗口显示所选内容中第一个字符的字体。
您可以覆盖此方法来更新字体窗口中附件视图的显示。
类似地,updateRuler使标尺显示所选内容中第一个段落的段落属性。
也可以替代此方法来自定义标尺中项目的显示。
一定要调用覆盖中的super实现,以便也执行基本更新。


自定义导入类型

NSTextView支持粘贴板操作以及将文件和颜色拖动到文本中。
如果您自定义子类处理新数据类型粘贴板操作的能力,则应覆盖readablePasteboardTypeswritablePasteboartTypes方法以反映这些类型。
同样,为了支持用于拖动操作的新数据类型,您应该重写acceptableDragTypes方法。
这些方法的实现应该调用超类实现,将新的数据类型添加到从super返回的数组中,并返回修改后的数组。

若要读取和写入自定义粘贴板类型,必须重写readSelectionFromPasteboard:type:writeSelectionToPasteboad:type:方法。
在这些方法的实现中,应该读取子类支持的新数据类型,并让超类处理任何其他类型。

对于拖动操作,如果子类接受自定义拖动类型的能力随着时间的推移而变化,则可以重写updateDragTypeRegistration以根据文本视图的当前状态注册或注销自定义类型。
默认情况下,如果接收器是可编辑的并且是富文本视图,则此方法可以拖动所有可接受的类型。


更改选择行为

NSTextView的子类可以自定义以编程方式设置焦点和选择 中描述的各种粒度(如字符、单词和段落)的选择方式。
在跟踪用户对所选内容的更改时,NSTextView对象会重复调用selectionRangeForProposedRange:granularity: 来确定实际选择的范围。
完成跟踪更改后,它会向代理发送一条textView:willChangeSelectionFromCharacterRange:toCharacterRange:消息。
通过重写NSTextView方法或实现委托方法,可以更改扩展或减少选择的方式。
例如,在代码编辑器中,您可以提供一个委托,该委托将双击大括号或圆括号字符扩展到其匹配的分隔符。

这些机制并不用于更改语言单词定义(例如双击所选内容)。
选择的细节在文本系统的较低级别(目前是私有级别)进行处理。


准备更改文本

如果创建NSTextView的子类以添加将根据用户操作更改文本的新功能,则可能需要在实际应用更改之前修改用户选择的范围。
例如,如果用户正在对标尺进行更改,则更改必须应用于整个段落,因此选择可能必须扩展到段落边界。
有三种方法可以计算出某些变化应该适用的范围。
rangeForUserTextChange方法返回对字符本身的任何更改(插入和删除)应适用的范围。
rangeForUserCharacterAttributeChange方法返回字符属性更改(如新字体或颜色)应应用的范围。
最后,rangeForUserParagraphAttributeChange返回段落级别更改的范围,例如新的或移动的制表位或缩进。
如果无法更改,这些方法都会返回一个位置为NSNotFound的范围;在这种情况下,您应该检查返回的范围并放弃更改。


文本更改通知和代理消息

在实际更改文本时,必须确保文本系统的不同部分正确执行和记录更改。
要做到这一点,请将每一批潜在更改用shouldChangeTextInRange:replacementString:didChangeText消息括起来。
这些方法确保发送适当的委托消息并发布通知。
第一个方法要求代理获得开始编辑textShouldBeginEdition:消息的权限。
如果委托返回NO,则shouldChangeTextInRange:replacementString:依次返回NO),在这种情况下,子类应禁止更改。
如果委托返回YES,则文本视图将发布NSTextDidBeginEditingNotificationshouldChangeTextInRange:replacementString:依次返回YES
在这种情况下,您可以对文本进行更改,然后调用didChangeText
此方法通过发布NSTextDidChangeNotification来结束更改,这将导致代理收到textDidChange:消息。

在编辑会话期间,textShouldBeginEdition:textDidBeginEditing:消息仅发送一次。
更准确地说,自NSTextView成为第一个响应程序以来,它们是在第一个用户输入时发送的。
此后,这些消息和NSTextDidBingEditingNotification将在序列中跳过。
但是,必须为每个单独的更改调用textView:shouldChangeTextInRange:replacementString:方法。


智能插入和删除

NSTextView定义了几种方法来帮助智能插入和删除文本,以便在更改后保留间距和标点符号。
智能插入和删除通常适用于用户选择了完整的单词或其他重要的文本单元。
例如,聪明地删除逗号前的单词也会删除逗号前留下的空格(尽管在剪切操作中不会将其放在粘贴板上)。
在另一个单词和逗号之间巧妙地插入一个单词会在两个单词之间增加一个空格,以保护边界。
NSTextView默认情况下自动使用智能插入和删除;可以使用setSmartInsertDeleteEnabled:关闭此行为。
这样做只会删除选定的文本,并添加插入的文本,而不添加空白。

如果NSTextView的子类定义了任何插入或删除文本的方法,则可以利用两个NSTextView方法使它们变得智能。
smartDeleteRangeForProposedRange:方法扩展了建议的删除范围,以包括也应删除的任何空白。
但是,如果需要保存已删除的文本,通常最好只保存原始范围中的文本。
对于智能插入,smartInsertForString:replacingRange:foreString:fafterString:通过引用返回两个字符串,您可以在给定字符串之前和之后插入这两个字符串以保留间距和标点符号。
有关详细信息,请参阅方法说明。


7、创建自定义文本视图

一种比子类化NSTextView更复杂的策略是创建自己的自定义文本视图对象。
如果您需要比NSTextView提供的更复杂的文本处理,例如在文字处理应用程序中,可以通过子类化NSView、实现NSTextInputClient协议并直接与输入管理系统交互来创建文本视图。


实现文本输入支持

自定义Cocoa视图可以为文本输入系统提供不同级别的支持。
基本上有三种支持级别可供选择:

1.覆盖keyDown:方法。
2.重写keyDown:并使用handleEvent:来支持密钥绑定。
3.还实现了完整的NSTextInputClient协议。

在第一级支持中,keyDown:方法识别有限的一组事件并忽略其他事件。
这种级别的支持是典型的游戏。
(当重写keyDown:时,还必须重写acceptsFirstResponder以使自定义视图响应关键事件,如[事件处理基础知识]中所述(https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/EventOverview/EventHandlingBasics/EventHandlingBasics.html#//apple_ref/doc/uid/10000060i-CH5)` 在 可可事件处理指南)中。

在第二级支持中,您可以覆盖keyDown:并使用handleEvent:方法来接收密钥绑定支持,而无需实现NSTextInputClient协议。
因为如果视图不符合NSTextInputClientNSView方法inputContext不会自动实例化NSTextInput Context,所以自定义视图必须手动实例化它。
然后实现视图想要支持的标准密钥绑定方法,如moveForward:deleteForward:
(密钥绑定方法的完整列表可在NSResponder.h中找到。)

如果您从头开始编写自己的文本视图,除了重写keyDown:和使用handleEvent:之外,还应该使用第三级支持并实现NSTextInputClient协议。
NSTextView及其子类是Cocoa中提供的唯一实现NSTextInputClient的类,如果您的应用程序需要比NSTextView所能提供的更复杂的行为,就像文字处理器一样,您可能需要从头开始实现文本视图。
要做到这一点,您必须将NSView子类化并实现NSTextInputClient协议。
(通过从NSTextView继承或直接实现该协议来实现该协议的类称为text view。)

如果您正在实现NSTextInputClient协议,那么您的视图需要管理标记的文本并与文本输入上下文通信,以支持文本输入系统。
这些任务将在接下来的两节中进行描述。


管理标记的文本

文本视图要与输入上下文协作,必须做的一件主要事情是在其文本存储中维护一个(可能为空)范围的标记文本
文本视图应以独特的方式突出显示此范围内的文本,并且应允许在标记的文本中进行选择。
文本视图还必须保持一个插入点,该插入点通常位于标记文本的末尾,但用户可以将其放置在标记文本中。
文本视图还在其文本存储中保留一个(可能为空)selection范围,如果有任何标记的文本,则该选择必须完全在标记的文本中。

当用户在NSTextView对象中多次按键输入字符(如é)时,会出现标记文本的常见示例。
要输入此字符,用户需要键入Option-E,然后键入E键。
按Option-E后,重音标记会出现在高亮显示的框中,表示文本已标记(不是最终文本)。
按下最后一个E后,é字符出现,高亮显示消失。


与文本输入语境的沟通

文本视图和文本输入上下文必须协作,这样输入上下文才能实现其用户界面。
NSTextInputContext类表示到文本输入系统的接口,即其客户端对象唯一的状态或上下文,例如密钥绑定状态、输入法通信会话等。
大多数NSTextInputClient协议方法由输入上下文调用,以操作文本视图中的文本,用于输入上下文的用户界面目的。

每个NSTextInputClient兼容对象(通常是NSView子类)都有自己的NSTextInput Context实例。
如果视图子类符合NSTextInputClient协议,NSView方法inputContext的默认实现将自动管理NSTextInput Context实例。

当鼠标或键盘事件发生时,文本视图必须通过向当前输入上下文发送handleEvent:消息来通知当前输入管理器。
当不再需要其标记的文本范围时,文本视图会向当前输入上下文发送一条discardMarkedText消息。

此外,文本视图必须通过向输入上下文发送invalidateCharacterCoordinates消息,告诉输入上下文字符范围的位置信息何时发生变化,例如文本视图何时滚动。
然后,输入上下文可以更新以前通过诸如firstRectForCharacterRange:actualRange:之类的方法查询的信息,例如,当它想要显示标记文本的选择弹出菜单时(与日语输入法一样)。
有一个可选的方法drawsVerticalyForCharacterAtIndex:,它可以通知文本输入系统,符合协议的客户端是否在给定索引处垂直呈现字符。

输入上下文通常使用NSTextInputClient协议中的所有方法。
当键盘布局发生更改时,您也可以注册以接收来自输入上下文的通知。

有关更多信息,请参阅*NSText类参考NSTextView类参考NSView类参考NSTextInputContext类引用,和NSTextInputClient协议参考*.


8、使用字段编辑器

本节介绍Cocoa文本系统如何使用字段编辑器,以及如何修改该行为。
在大多数情况下,您不需要关心字段编辑器,因为Cocoa在幕后自动处理其操作。
然而,知道它的存在是件好事,在某些情况下,你可能想改变它的行为。


字段编辑器的工作原理

当用户开始编辑NSControl对象(如文本字段)的文本时,文本系统自动从NSTextView类实例化字段编辑器。
在进行编辑时,系统会将字段编辑器作为第一个响应程序插入到响应程序链中,因此它会接收击键事件来代替文本字段或其他控制对象。
当焦点转移到另一个文本字段时,字段编辑器会将自己附加到该字段。
字段编辑器将当前文本字段指定为其代理,从而使文本字段能够控制对其内容的更改。
如果您不熟悉字段编辑器的工作原理,这种机制可能会令人困惑,因为NSWindow方法firstResponder返回的是不可见的字段编辑器,而不是当前具有键盘焦点的屏幕对象。

字段编辑器的其他职责包括维护其编辑的文本字段的选择。
因此,未编辑的文本字段没有选择(除非缓存它)。

字段编辑器是通过在文本输入过程中处理某些字符来定义的,这与普通的文本视图不同。
普通文本视图在用户按Return或Enter时插入换行符,在用户按tab时插入制表符,并忽略Shift tab。
相反,字段编辑器将这些字符解释为结束编辑并放弃第一响应者状态的提示,将焦点转移到键视图循环中的下一个对象(或者在Shift Tab的情况下,转移到上一个键视图)。

编辑结束触发字段编辑器的内容与控制文本字段中编辑的NSTextFieldCell对象的同步。
此时,Cocoa将字段编辑器与文本字段分离,并显示视图层次结构顶部的文本字段。


在字段编辑器中使用委派和通知

控制文本字段编辑行为的方法之一是通过委派和通知与字段编辑器交互。
由于字段编辑器会自动将正在编辑的任何文本字段指定为其代理,因此通常可以将文本字段的特殊编辑行为封装为文本字段本身。


更改默认行为

通过实现委托方法可以直接更改字段编辑器的默认行为。
例如,代理可以更改用户在编辑文本视图时按Return键时发生的行为。
默认情况下,该操作将结束编辑并选择关键点视图循环中的下一个控件。
例如,如果您希望按Return结束编辑,但不选择下一个控件,则可以在文本字段中实现textDidEndEdition:委托方法。
如果委托实现了此方法,则字段编辑器会自动调用该方法,并传递NSTextDidEndEditingNotification
实现可以检查此通知以发现结束编辑的事件并做出适当的响应。


在NSTextField对象中获取换行符

用户可以通过按Option Return或Option Enter轻松地将换行符放入文本字段。
但是,在某些情况下,您可能希望允许用户在不采取任何特殊操作的情况下输入换行符,并且您可以通过实现委托方法来实现这一点。

最简单的方法是在窗口的字段编辑器上调用setFieldEditor:NO
但是,当然,这种方法会更改所有控件的字段编辑器的行为。
另一种方法是使用NSControl委托消息control:textShouldBeginEdition:,当用户在文本字段中输入字符时,该消息将发送给文本视图的委托。
因为它将引用传递给文本视图和字段编辑器,所以您可以测试文本视图是否是要输入换行符的视图,然后简单地将setFieldEditor:NO发送到字段编辑器。
但是,只有在用户向文本字段中输入一个字符之后,才会调用此方法,如果该字符是换行符,则会被拒绝。

一个更好的方法是实现另一个NSControl委托方法control:textView:doCommandBySelector:,它使文本字段的委托能够检查用户是否试图插入换行符,如果是,则强制字段编辑器插入。
实现可能如清单7-3所示。

例7-3 强制字段编辑器输入换行符

- (BOOL)control:(NSControl *)control textView:(NSTextView *)fieldEditor
                                    doCommandBySelector:(SEL)commandSelector {
     BOOL retval = NO;
     if (commandSelector == @selector(insertNewline:)) {
         retval = YES;
         [fieldEditor insertNewlineIgnoringFieldEditor:nil];
     }
     return retval;
}

此方法返回YES表示它处理此特定命令,返回NO表示它不处理的其他命令。
这种方法的优点是,它不会更改字段编辑器的设置,只处理感兴趣的特殊情况。
因为委托消息包括对正在编辑的控件的引用,所以可以添加一个检查,将行为限制到特定的类,如NSTextField或单个子类。


使用自定义字段编辑器

要以超越委托所能做的方式自定义行为,您需要定义NSTextView的子类,该子类包含您的专用行为,并将其替换为窗口的默认字段编辑器。


为什么要使用自定义字段编辑器?

如果您只需要在用户键入时验证、解释、格式化甚至编辑文本字段的内容,则不必使用自定义字段编辑器。
为此,您可以附加NSFormatter,如NSNumberFormatterNSDateFormatter或自定义格式化程序。
请参阅*数据格式指南*有关使用格式化程序的详细信息。
授权和通知也为您提供了许多干预机会,如使用字段编辑器的授权和通知 中所述。

安全文本字段是一个真正专门处理数据的例子,它超出了格式化程序或委托可以合理处理的范围。
安全文本字段必须接受用户输入的文本数据并验证条目,这可以通过常规文本字段和格式化程序轻松完成。
但是,它必须显示一些伪字符以对真实数据保密,同时保留真实数据用于身份验证过程或其他目的。
此外,安全文本字段必须通过禁用复制和剪切等功能,并可能对数据进行加密,来保护其数据免受未经授权的访问。
要实现这些专门的需求,部署自定义字段编辑器是最简单的。
事实上,Cocoa在NSSecureTextField类中实现了一个自定义字段编辑器。

任何需要对输入文本字段的数据进行异常处理的情况,或其他无法通过标准Cocoa机制获得的个性化行为,都是自定义字段编辑器的好选择。


如何替换自定义字段编辑器

通过实现NSWindow委托方法windowWillReturnFieldEditor:toObject:,可以用自定义字段编辑器代替窗口的默认版本。
您可以在窗口的委托中实现此方法,例如,该委托可以是窗口控制器对象。
窗口将此消息发送给其委托,并将其自身和请求字段编辑器的对象作为参数。
因此,您可以测试对象,并根据结果替换自定义字段编辑器。
对于其他控件,该窗口将继续使用其默认字段编辑器。

例如,清单7-4中所示的实现测试请求对象是否是从名为“CustomTextField”的自定义文本字段类实例化的,如果是,则返回一个自定义字段编辑器。


- (id)windowWillReturnFieldEditor:(NSWindow *)sender toObject:(id)anObject
{
    if ([anObject isKindOfClass:[CustomTextField class]])
    {
        if (!myCustomFieldEditor) {
        myCustomFieldEditor = [[CustomFieldEditor alloc] init];
        [myCustomFieldEditor setFieldEditor:YES];
        }
        return myCustomFieldEditor;
    }
    return nil;
}

如果请求对象不是自定义文本字段或子类,则代理方法返回nil,窗口使用其默认字段编辑器。
这种安排的优点是,除非需要,否则不会实例化自定义字段编辑器。

在 OS X v10.6 及更高版本中,提供自定义字段编辑器的另一种方法是重写NSCellfieldEditorForView:方法。
此方法(而不是窗口代理方法)更适合自定义单元格子类。

您可以在Subclassing NSTextView中找到有关NSTextView子类化的更多信息。


字段编辑器相关方法

本节列出了与字段编辑器最直接相关的 AppKit 方法。
您可以仔细阅读这些表格,了解 Cocoa 为您提供与字段编辑器交互的机会。
有关详细信息,请参阅 Application Kit Framework 参考
NSWindow字段编辑器相关的方法列于表7-1中。


Table 7-1 NSWindow field editor–related methods

方法描述
fieldEditor:forObject:返回接收器的字段编辑器,如果需要则创建它。
endEditingFor:强制现场编辑器放弃其第一响应者状态并为下一个任务做好准备。
windowWillReturnFieldEditor:toObject:当对象请求发送者的字段编辑器时调用代理方法。
如果代理的此方法实现返回除 之外的对象nilNSWindow则将其替换为字段编辑器。

NSTextFieldCell字段编辑器相关的方法如表7-2所列。

Table 7-2 NSTextFieldCell field editor–related method

方法描述
setUpFieldEditorAttributes:您永远不会直接调用此方法;但是,通过覆盖它,您可以自定义字段编辑器。

NSCell字段编辑器相关的方法列于表7-3中。

Table 7-3 NSCell field editor–related methods

方法描述
fieldEditorForView:在 OS X v10.6 及更高版本中替换自定义字段编辑器的主要方法。
selectWithFrame:inView:editor:delegate:start:length:使用随消息传递的字段编辑器来选择一定范围内的文本。
editWithFrame:inView:editor:delegate:event:开始使用随消息传递的字段编辑器编辑接收者的文本。
endEditing:结束任何编辑文本,使用随消息传递的字段编辑器,以另外两种与NSCell字段编辑器相关的方法开始。

表 7-4列出了与NSControl字段编辑器相关的方法。
表 7-4 列出的NSControl代理方法是以下方法定义的代理方法和通知的控件特定版本:NSText
字段编辑器派生自NSText,通过其编辑操作发起发送代理消息和通知。

Table 7-4 NSControl field editor–related methods

方法描述
abortEditing终止并放弃接收器显示的文本的任何编辑并删除字段编辑器的代理。
currentEditor如果接收器正在被编辑,则此方法返回字段编辑器;否则,返回nil
validateEditing将接收控件单元格中文本的对象值设置为单元格字段编辑器的当前内容。
control:textShouldBeginEditing:当用户尝试在随消息传递的控件的单元格中输入字符时直接发送给代理。
control:textShouldEndEditing:当插入点试图离开已被编辑的控件的单元格时直接发送给代理。
controlTextDidBeginEditing:当控件开始编辑文本时,由默认通知中心发送给代理(以及通知的所有观察者),并传递NSControlTextDidBeginEditingNotification
controlTextDidChange:当接收控件中的文本发生变化时,由默认通知中心发送给代理和观察者,并传递NSControlTextDidChangeNotification
controlTextDidEndEditing:当控件结束编辑文本时,由默认通知中心发送给代理和观察者,并传递NSControlTextDidEndEditingNotification

NSResponder字段编辑器相关的方法列于表7-5中。

Table 7-5 NSResponder field editor–related methods

方法描述
insertBacktab:由子类实现来处理“后退选项卡”。
insertNewlineIgnoringFieldEditor:由子类实现,在插入点或选择处插入换行符。
insertTabIgnoringFieldEditor:由子类实现,在插入点或选择处插入制表符。

NSText字段编辑器相关的属性和NSTextDelegate方法列于表7-6中。

Table 7-6 NSText field editor–related methods

方法描述
isFieldEditorYES如果接收方将 Tab、Shift-Tab 和 Return(Enter)解释为结束编辑的提示并可能更改第一响应者,则返回;NO如果它接受它们作为文本输入。
setFieldEditor:控制接收者是否将 Tab、Shift-Tab 和 Return (Enter) 解释为结束编辑并可能更改第一响应者的提示。
textDidBeginEditing:通知代表用户已开始更改文本,传递NSTextDidBeginEditingNotification
textDidChange:通知代理文本对象已改变其字符或格式属性,并传递NSTextDidChangeNotification
textDidEndEditing:通知代理文本对象已完成编辑(已辞去第一响应者状态),传递NSTextDidEndEditingNotification
textShouldBeginEditing:从文本对象的实现调用becomeFirstResponder,此方法请求开始编辑的权限。
textShouldEndEditing:从文本对象的实现调用resignFirstResponder,此方法请求结束编辑的权限。

2024-06-09

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/712567.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

web端即时通信技术

web端即时通信技术 对于IM/消息推送这类即时通讯系统而言&#xff0c;系统的关键就是“实时通信”能力。所谓实时通信有以下两层含义 客户端可以主动向服务端发送信息。 当服务端内容发生变化时&#xff0c;服务端可以实时通知客户端。 HTTP局限 Http是客户端/服务器模式中…

AI实践与学习6-RAG流程优化学习

背景 RAG流程很多细节优化点&#xff0c;助力AIGC。 内容 LangChain在RAG功能上的一些能力 多路向量检索 多向量检索器的核心想法是将我们想要用于答案合成的文档与我们想要用于检索的参考文献分开。这允许系统为搜索优化文档的版本&#xff08;例如&#xff0c;摘要&…

LeetCode | 709.转换成小写字母

这道题可以用api也可以自己实现&#xff0c;都不难&#xff0c;大小字母之前相差了32&#xff0c;检查到大写字母时加上32即可 class Solution(object):def toLowerCase(self, s):""":type s: str:rtype: str"""return s.lower()class Solution…

笨蛋学算法之LeetCodeHot100_5_三数之和(Java)

package com.lsy.leetcodehot100;import java.util.ArrayList; import java.util.Arrays; import java.util.List;public class _Hot6_三数之和 {public static List<List<Integer>> threeSum(int[] nums) {//先排序数组Arrays.sort(nums);//存放结果集List<Lis…

QShop商城-短信通知配置

QShop商城-短信通知配置 本系统短信通知配置可选阿里云/腾讯云,二者二选一即可. 阿里云短信 一、登录阿里云短信平台 阿里云短信平台管理地址&#xff1a;https://dysms.console.aliyun.com/dysms.html 二、账户ID和秘钥&#xff08;AccessKeyId 和 AccessKeySecret&#x…

C++初学者指南第一步---2. Hello world

C初学者指南第一步—2. Hello world 目录 C初学者指南第一步---2. Hello world1.源文件 “Hello.cpp”2.编译hello.cpp3.术语4.编译器标志5.不要使用 “using namespace std;” &#xff01; 1.源文件 “Hello.cpp” #include <iostream> // our first program int main…

检索增强生成(RAG)实践:基于LlamaIndex和Qwen1.5搭建智能问答系统

什么是 RAG LLM 会产生误导性的 “幻觉”&#xff0c;依赖的信息可能过时&#xff0c;处理特定知识时效率不高&#xff0c;缺乏专业领域的深度洞察&#xff0c;同时在推理能力上也有所欠缺。 正是在这样的背景下&#xff0c;检索增强生成技术&#xff08;Retrieval-Augmented…

无人机的发展

朋友们&#xff0c;你们知道吗&#xff1f;无人机的发展之路可谓是科技界的一股清流&#xff0c;风头正劲啊&#xff01;从最初简单的遥控飞机到现在各种智能功能的加持&#xff0c;无人机真是越来越神奇了&#xff01; 首先&#xff0c;无人机在航拍领域大放异彩&#xff01;无…

FullCalendar日历组件集成实战(14)

背景 有一些应用系统或应用功能&#xff0c;如日程管理、任务管理需要使用到日历组件。虽然Element Plus也提供了日历组件&#xff0c;但功能比较简单&#xff0c;用来做数据展现勉强可用。但如果需要进行复杂的数据展示&#xff0c;以及互动操作如通过点击添加事件&#xff0…

Linux Centos 环境下搭建RocketMq集群(双主双从)

1、下载rocketmq的包 下载 | RocketMQ 2、配置环境变量 1、编辑环境变量文件&#xff1a;vim /etc/profile2、加入如下配置&#xff1a; #rocketmq 4.9.8 ROCKETMQ_HOME/home/rocketmq/rocketmq-4.9.8 export PATH${ROCKETMQ_HOME}/bin:${PATH}3、刷新配置&#xff1a;source…

OpenWrt配置单臂路由模式

正文共&#xff1a;888 字 24 图&#xff0c;预估阅读时间&#xff1a;1 分钟 前面&#xff0c;我们成功将OpenWrt部署到了x86的ESXi服务器中&#xff08;将OpenWrt部署在x86服务器上&#xff09;&#xff0c;但是我们没有设置root密码&#xff0c;非常不安全。赶紧在“system”…

蚓链数字化营销教你寻找快准直达市场路径小绝招

在当今数字化的商业世界中&#xff0c;蚓链数字化营销成为了企业开拓市场、实现增长的有力工具。它犹如一盏明灯&#xff0c;为您照亮寻找快速直达市场路径的方向。 绝招一&#xff1a;深入的市场调研。利用蚓链数字化营销的大数据分析能力&#xff0c;全面了解目标市场的规模、…

django.db.utils.NotSupportedError: MySQL 8 or later is required (found 5.7.33).

django.db.utils.NotSupportedError: MySQL 8 or later is required (found 5.7.33). 一、原因分析 在新版的Django默认需要MySQL 8或更高版本&#xff0c;才能运行。 二、解决办法 1、升级mysql数据库版本 只需要将mysql版本升级到8.0&#xff0c;即可解决&#xff0c;当然这…

护眼灯落地的好还是桌面的好?落地护眼灯性价比高的品牌推荐

护眼灯落地的好还是桌面的好&#xff1f;当我们为了更好地保护眼睛而选择护眼灯时&#xff0c;常常会面临一个纠结的问题&#xff1a;到底是护眼灯落地的好还是桌面的好呢&#xff1f;这看似是一个简单的二选一&#xff0c;实则背后蕴含着诸多需要深入探讨的因素。 护眼灯的选择…

申请国外访问学者面签技巧有哪些?

申请国外访问学者面签是一项重要的步骤&#xff0c;关系到能否成功获得访问学者身份。以下是一些实用的面签技巧&#xff0c;帮助您顺利通过面试。 1.充分准备材料 成功的面签始于准备充分的材料。确保您的申请材料齐全&#xff0c;包括&#xff1a; 个人简历&#xff1a;突出…

业务动态校验框架应用实现

目录 一、业务背景 二、配置内容展示 三、商品动态配置内容展示 &#xff08;一&#xff09;商品spu校验信息数据 &#xff08;二&#xff09;商品sku校验信息数据 &#xff08;三&#xff09;组包商品校验信息数据 &#xff08;四&#xff09;商品数据校验数据持有者 &…

mac下Xcode在iphone真机上测试运行iOS软件

最近一个需求需要在iPhone真机上测试一个视频直播的项目。 需要解决如何将项目 app 安装到真机上 在进行真机调试。 安装Xcode 直接在App Store上搜索Xcode安装即可。 关键是要安装Simulator。项目需要安装iOS17.5但是由于安装包太大&#xff0c;并且网络不稳定的原因。在Xco…

B+索引的分裂及选择率和索引基数

1、B树索引的分裂 B树索引页的分裂并不总是从页的中间记录开始&#xff0c;这样可能会导致页空间的浪费。 例子 比如下面这个记录&#xff1a; 1、2、3、4、5、6、7、8、9 由于插入是以自增的顺序进行的&#xff0c;若这时插入第10条记录然后进行页的分裂操作&#xff0c;那…

算法:位运算题目练习

目录 常见的位运算的操作总结 ①基础位操作 ②给一个数n&#xff0c;确定它的二进制表示中的第x位是0还是1 ③将一个数n的二进制表示的第x位修改成1 ④将一个数n的二进制表示的第x位修改成0 ⑤位图的思想 ⑥提取一个数n二进制表示中最右侧的1 ⑦干掉一个数n二进制表示中…

激活和禁用Hierarchy面板上的物体

1、准备工作&#xff1a; (1) 在HIerarchy上添加待隐藏/显示的物体&#xff0c;名字自取。如&#xff1a;endImage (2) 在Inspector面板&#xff0c;该物体的名称前取消勾选&#xff08;隐藏&#xff09; (3) 在HIerarchy上添加按钮&#xff0c;名字自取。如&#xff1a;tip…